import { Download } from 'lucide-react';
import { toast } from 'sonner';

import type { AnchorHTMLAttributes, MouseEvent } from 'react';
import { useState } from 'react';

import { cn } from '@/lib/utils';

type Variant = 'primary' | 'subtle';
type Size = 'sm' | 'md';

interface DownloadPluginButtonProps
  extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href' | 'download'> {
  variant?: Variant;
  size?: Size;
  label?: string;
}

const variantClasses: Record<Variant, string> = {
  primary:
    'bg-primary text-primary-foreground hover:bg-primary/90 active:scale-[0.98] transition-transform',
  subtle: 'text-primary underline underline-offset-2 hover:text-primary/80',
};

const sizeClasses: Record<Size, string> = {
  sm: 'gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium',
  md: 'gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium',
};

const ZIP_MIME = 'application/zip';

/**
 * Pretty error copy keyed off the response status. Stays terse so the toast
 * fits on small viewports, but tells the user enough to know what failed.
 */
function describeFailure(status: number): string {
  if (status === 429) {
    return 'Download rate-limited. Try again in an hour.';
  }
  if (status === 404) {
    return 'Plugin zip is not available yet. Contact support if this persists.';
  }
  if (status >= 500) {
    return 'Plugin download is temporarily unavailable. Try again shortly.';
  }
  return `Could not download the plugin (HTTP ${status}).`;
}

/**
 * Single source of truth for the WP-plugin download CTA.
 *
 * Used in both the onboarding wizard's "install plugin" step and the site
 * connection card so the URL, file naming, and styling can't drift apart.
 *
 * Download flow:
 *   1. Pre-flight the endpoint with `fetch()` to read status + Content-Type
 *      before letting the browser save anything. Until 2026-05 the button
 *      was a plain `<a download>` that blindly saved whatever the server
 *      returned — on 404 the user got a 25-byte text response saved as
 *      `rankwiz-ai-plugin.zip` (a fake zip, hence the "empty file name"
 *      bug reported repeatedly). The fetch-first preflight ensures the
 *      browser only sees a real zip.
 *   2. On 2xx + zip Content-Type, turn the response into an Object URL and
 *      programmatically click an anchor with the server's Content-Disposition
 *      filename (or a `rankwiz-ai-plugin.zip` fallback) to save.
 *   3. On non-zip / non-2xx, fire a toast describing the failure and never
 *      let the browser save the response body.
 *
 * The button is still rendered as an `<a>` element with the canonical href +
 * download attribute so middle-click / right-click "Save link as…" / SSR /
 * keyboard "Enter" all still behave as a fall-back direct link — important
 * because some download managers don't run JS and rely on `<a download>`.
 */
export default function DownloadPluginButton({
  variant = 'primary',
  size = 'sm',
  label = 'Download plugin (.zip)',
  className,
  onClick,
  ...rest
}: DownloadPluginButtonProps): JSX.Element {
  const [isLoading, setIsLoading] = useState(false);
  const isAnchorStyle = variant === 'subtle';
  const href = route('plugin.download');

  async function handleClick(event: MouseEvent<HTMLAnchorElement>): Promise<void> {
    // Honor any caller-provided onClick (analytics, etc.) first.
    if (onClick) {
      onClick(event);
    }
    // Modifier-click / non-primary click should fall through to native <a>
    // behavior so the user can "open in new tab" / "save as" if they prefer.
    if (
      event.defaultPrevented ||
      event.metaKey ||
      event.ctrlKey ||
      event.shiftKey ||
      event.altKey ||
      event.button !== 0
    ) {
      return;
    }
    event.preventDefault();

    if (isLoading) {
      return;
    }

    setIsLoading(true);
    try {
      const response = await fetch(href, {
        method: 'GET',
        credentials: 'same-origin',
        cache: 'no-store',
        headers: { Accept: ZIP_MIME },
      });

      const contentType = response.headers.get('Content-Type') ?? '';
      if (!response.ok || !contentType.includes(ZIP_MIME)) {
        toast.error(describeFailure(response.status));
        return;
      }

      const blob = await response.blob();
      // Extract filename from Content-Disposition (rankwiz-ai-X.Y.Z.zip).
      // Anything malformed falls back to the generic plugin name so the saved
      // file at least carries a .zip extension.
      const disposition = response.headers.get('Content-Disposition') ?? '';
      const filenameMatch = /filename=(?:"([^"]+)"|([^;\s]+))/i.exec(disposition);
      const filename =
        filenameMatch?.[1] ?? filenameMatch?.[2] ?? 'rankwiz-ai-plugin.zip';

      const objectUrl = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = objectUrl;
      a.download = filename;
      a.rel = 'noopener';
      document.body.appendChild(a);
      a.click();
      a.remove();
      // Defer URL revocation so the browser has time to read the blob.
      window.setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
    } catch (err) {
      const message = err instanceof Error ? err.message : 'Network error';
      toast.error(`Could not download the plugin: ${message}`);
    } finally {
      setIsLoading(false);
    }
  }

  return (
    <a
      href={href}
      // Keep `download` as a safe fallback for the no-JS / right-click path —
      // pinning `.zip` so the saved file at least has the correct extension
      // when the browser bypasses our onClick handler.
      download="rankwiz-ai-plugin.zip"
      onClick={(event) => {
        void handleClick(event);
      }}
      aria-busy={isLoading || undefined}
      aria-disabled={isLoading || undefined}
      className={cn(
        'inline-flex items-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
        !isAnchorStyle && sizeClasses[size],
        variantClasses[variant],
        isLoading && 'pointer-events-none opacity-70',
        className,
      )}
      {...rest}
    >
      <Download className={size === 'sm' ? 'size-3.5' : 'size-4'} aria-hidden="true" />
      {isLoading ? 'Preparing…' : label}
    </a>
  );
}
