import React, { useState, useCallback, useEffect, useMemo } from 'react';
import {
  Dialog,
  DialogContent,
  DialogTitle,
} from '@/components/ui/dialog';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import { SimpleMarkdownRenderer } from '@/components/chat/MarkdownRenderer';
import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import type { UpdateInfo, UpdateProgress } from '@/lib/desktop';
import { copyTextToClipboard } from '@/lib/clipboard';
import { openExternalUrl } from '@/lib/url';
import { useI18n } from '@/lib/i18n';

type WebUpdateState = 'idle' | 'updating' | 'restarting' | 'reconnecting' | 'error';

interface UpdateDialogProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  info: UpdateInfo | null;
  downloading: boolean;
  downloaded: boolean;
  progress: UpdateProgress | null;
  error: string | null;
  onDownload: () => void;
  onRestart: () => void;
  /** Runtime type to show different UI for desktop vs web */
  runtimeType?: 'desktop' | 'web' | 'vscode' | null;
}

const GITHUB_RELEASES_URL = 'https://github.com/btriapitsyn/openchamber/releases';

type ChangelogSection = {
  version: string;
  date: string;
  start: number;
  end: number;
  raw: string;
};

type ParsedChangelog =
  | {
      kind: 'raw';
      title: string;
      content: string;
    }
  | {
      kind: 'sections';
      title: string;
      sections: Array<{ version: string; dateLabel: string; content: string }>;
    };

function formatIsoDateForUI(isoDate: string): string {
  const d = new Date(`${isoDate}T00:00:00`);
  if (Number.isNaN(d.getTime())) {
    return isoDate;
  }
  return new Intl.DateTimeFormat(undefined, {
    year: 'numeric',
    month: 'short',
    day: 'numeric',
  }).format(d);
}

function stripChangelogHeading(sectionRaw: string): string {
  return sectionRaw.replace(/^## \[[^\]]+\] - \d{4}-\d{2}-\d{2}\s*\n?/, '').trim();
}

function processChangelogMentions(content: string): string {
  // Convert @username to markdown links so they can be styled via css
  return content.replace(/(^|[^a-zA-Z0-9])@([a-zA-Z0-9-]+)/g, '$1[@$2](https://github.com/$2)');
}

function compareSemverDesc(a: string, b: string): number {
  const pa = a.split('.').map((v) => Number.parseInt(v, 10));
  const pb = b.split('.').map((v) => Number.parseInt(v, 10));
  for (let i = 0; i < 3; i += 1) {
    const da = Number.isFinite(pa[i]) ? (pa[i] as number) : 0;
    const db = Number.isFinite(pb[i]) ? (pb[i] as number) : 0;
    if (da !== db) {
      return db - da;
    }
  }
  return 0;
}

function parseChangelogSections(body: string): ChangelogSection[] {
  const re = /^## \[(\d+\.\d+\.\d+)\] - (\d{4}-\d{2}-\d{2})\s*$/gm;
  const matches: Array<{ version: string; date: string; start: number }> = [];

  let m: RegExpExecArray | null;
  while ((m = re.exec(body)) !== null) {
    matches.push({
      version: m[1] ?? '',
      date: m[2] ?? '',
      start: m.index,
    });
  }

  if (matches.length === 0) {
    return [];
  }

  return matches.map((match, idx) => {
    const end = matches[idx + 1]?.start ?? body.length;
    const raw = body.slice(match.start, end).trim();
    return { version: match.version, date: match.date, start: match.start, end, raw };
  });
}

type InstallWebUpdateResult = {
  success: boolean;
  error?: string;
  autoRestart?: boolean;
};

const WEB_UPDATE_POLL_INTERVAL_MS = 2000;
const WEB_UPDATE_MAX_WAIT_MS = 10 * 60 * 1000;

async function installWebUpdate(): Promise<InstallWebUpdateResult> {
  try {
    const response = await fetch('/api/openchamber/update-install', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
    });

    if (!response.ok) {
      const data = await response.json().catch(() => ({}));
      return { success: false, error: data.error || `Server error: ${response.status}` };
    }

    const data = await response.json().catch(() => ({}));
    return {
      success: true,
      autoRestart: data.autoRestart !== false,
    };
  } catch (error) {
    return { success: false, error: error instanceof Error ? error.message : undefined };
  }
}

async function isServerReachable(): Promise<boolean> {
  try {
    const response = await fetch('/health', {
      method: 'GET',
      headers: { Accept: 'application/json' },
    });
    return response.ok;
  } catch {
    return false;
  }
}

async function waitForUpdateApplied(
  previousVersion?: string,
  maxAttempts = Math.ceil(WEB_UPDATE_MAX_WAIT_MS / WEB_UPDATE_POLL_INTERVAL_MS),
  intervalMs = WEB_UPDATE_POLL_INTERVAL_MS,
): Promise<boolean> {
  for (let i = 0; i < maxAttempts; i++) {
    try {
      const response = await fetch('/api/openchamber/update-check', {
        method: 'GET',
        headers: { Accept: 'application/json' },
      });
      if (response.ok) {
        const data = await response.json().catch(() => null);
        if (data && data.available === false) {
          return true;
        }
        if (
          data &&
          typeof data.currentVersion === 'string' &&
          typeof previousVersion === 'string' &&
          data.currentVersion !== previousVersion
        ) {
          return true;
        }
      } else if ((response.status === 401 || response.status === 403) && await isServerReachable()) {
        return true;
      }
    } catch {
      // Server may be restarting
    }
    await new Promise(resolve => setTimeout(resolve, intervalMs));
  }
  return false;
}

export const UpdateDialog: React.FC<UpdateDialogProps> = ({
  open,
  onOpenChange,
  info,
  downloading,
  downloaded,
  progress,
  error,
  onDownload,
  onRestart,
  runtimeType = 'desktop',
}) => {
  const { t } = useI18n();
  const [copied, setCopied] = useState(false);
  const [webUpdateState, setWebUpdateState] = useState<WebUpdateState>('idle');
  const [webError, setWebError] = useState<string | null>(null);

  const releaseUrl = info?.version
    ? `${GITHUB_RELEASES_URL}/tag/v${info.version}`
    : GITHUB_RELEASES_URL;

  const progressPercent = progress?.total
    ? Math.round((progress.downloaded / progress.total) * 100)
    : 0;

  const isWebRuntime = runtimeType === 'web';
  const updateCommand = info?.updateCommand || 'openchamber update';

  // Reset state when dialog closes
  useEffect(() => {
    if (!open) {
      setWebUpdateState('idle');
      setWebError(null);
    }
  }, [open]);

  const handleCopyCommand = async () => {
    const result = await copyTextToClipboard(updateCommand);
    if (result.ok) {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    }
  };

  const handleOpenExternal = useCallback(async (url: string) => {
    await openExternalUrl(url);
  }, []);
  const handleWebUpdate = useCallback(async () => {
    setWebUpdateState('updating');
    setWebError(null);

    const result = await installWebUpdate();

    if (!result.success) {
      setWebUpdateState('error');
      setWebError(result.error || t('updateDialog.error.updateFailed'));
      return;
    }

    if (result.autoRestart) {
      setWebUpdateState('restarting');
      await new Promise(resolve => setTimeout(resolve, 2000));
    }

    setWebUpdateState('reconnecting');

    const applied = await waitForUpdateApplied(info?.currentVersion);

    if (applied) {
      window.location.reload();
    } else {
      setWebUpdateState('error');
      setWebError(t('updateDialog.error.takingLonger'));
    }
  }, [info?.currentVersion, t]);

  const isWebUpdating = webUpdateState !== 'idle' && webUpdateState !== 'error';

  const changelog = useMemo<ParsedChangelog | null>(() => {
    if (!info?.body) {
      return null;
    }

    const body = info.body.trim();
    if (!body) {
      return null;
    }

    const sections = parseChangelogSections(body);

    if (sections.length === 0) {
      return {
        kind: 'raw',
        title: t('updateDialog.changelog.title'),
        content: processChangelogMentions(body),
      };
    }

    const sorted = [...sections].sort((a, b) => compareSemverDesc(a.version, b.version));
    return {
      kind: 'sections',
      title: t('updateDialog.changelog.title'),
      sections: sorted.map((section) => ({
        version: section.version,
        dateLabel: formatIsoDateForUI(section.date),
        content: processChangelogMentions(stripChangelogHeading(section.raw) || body),
      })),
    };
  }, [info?.body, t]);

  return (
    <Dialog open={open} onOpenChange={isWebUpdating ? undefined : onOpenChange}>
      <DialogContent className="max-w-4xl p-5 bg-background border-[var(--interactive-border)]" showCloseButton={true}>
        
        {/* Header Section */}
        <div className="flex items-center mb-1">
          <DialogTitle className="flex items-center gap-2.5">
            <Icon name="download-cloud" className="h-5 w-5 text-[var(--primary-base)]" />
            <span className="text-lg font-semibold text-foreground">
              {webUpdateState === 'restarting' || webUpdateState === 'reconnecting'
                ? t('updateDialog.header.updating')
                : t('updateDialog.header.updateAvailable')}
            </span>
          </DialogTitle>

          {/* Version Diff */}
          {(info?.currentVersion || info?.version) && (
            <div className="flex items-center gap-2 font-mono text-sm ml-3">
              {info?.currentVersion && (
                <span className="text-muted-foreground">{info.currentVersion}</span>
              )}
              {info?.currentVersion && info?.version && (
                <span className="text-muted-foreground/50">→</span>
              )}
              {info?.version && (
                <span className="text-[var(--primary-base)] font-medium">{info.version}</span>
              )}
            </div>
          )}
        </div>

        {/* Content Body */}
        <div className="space-y-2">

          {/* Web update progress */}
          {isWebRuntime && isWebUpdating && (
            <div className="rounded-lg bg-[var(--surface-elevated)]/30 p-5 border border-[var(--surface-subtle)]">
              <div className="flex items-center gap-3">
                <Icon name="loader" className="h-5 w-5 animate-spin text-[var(--primary-base)]" />
                <div className="typography-ui-label text-foreground">
                  {webUpdateState === 'updating' && t('updateDialog.status.installingUpdate')}
                  {webUpdateState === 'restarting' && t('updateDialog.status.serverRestarting')}
                  {webUpdateState === 'reconnecting' && t('updateDialog.status.waitingForServer')}
                </div>
              </div>
              <p className="mt-2 text-xs text-muted-foreground">
                {t('updateDialog.status.autoReloadHint')}
              </p>
            </div>
          )}

          {/* Changelog Rendering */}
          {changelog && !isWebUpdating && (
            <div className="rounded-lg border border-[var(--surface-subtle)] bg-[var(--surface-elevated)]/20 overflow-hidden">
              <ScrollableOverlay
                className="max-h-[400px] p-0"
                fillContainer={false}
              >
                {changelog.kind === 'raw' ? (
                  <div
                    className="p-4 typography-markdown-body text-foreground leading-relaxed break-words [&_a]:!text-[var(--primary-base)] [&_a]:!no-underline [&_a:hover]:!underline"
                    onClickCapture={(e) => {
                      const target = e.target as HTMLElement;
                      const a = target.closest('a');
                      if (a && a.href) {
                        e.preventDefault();
                        e.stopPropagation();
                        void handleOpenExternal(a.href);
                      }
                    }}
                  >
                    <SimpleMarkdownRenderer content={changelog.content} disableLinkSafety={true} />
                  </div>
                ) : (
                  <div className="divide-y divide-[var(--surface-subtle)]">
                    {changelog.sections.map((section) => (
                      <div key={section.version} className="p-4">
                        <div className="flex items-center gap-3 mb-3">
                          <span className="typography-ui-label font-mono text-[var(--primary-base)] bg-[var(--primary-base)]/10 px-1.5 py-0.5 rounded">
                            v{section.version}
                          </span>
                          <span className="text-sm font-medium text-muted-foreground">
                            {section.dateLabel}
                          </span>
                        </div>
                        <div
                          className="typography-markdown-body text-foreground leading-relaxed break-words [&_a]:!text-[var(--primary-base)] [&_a]:!no-underline [&_a:hover]:!underline"
                          onClickCapture={(e) => {
                            const target = e.target as HTMLElement;
                            const a = target.closest('a');
                            if (a && a.href) {
                              e.preventDefault();
                              e.stopPropagation();
                              void handleOpenExternal(a.href);
                            }
                          }}
                        >
                          <SimpleMarkdownRenderer content={section.content} disableLinkSafety={true} />
                        </div>
                      </div>
                    ))}
                  </div>
                )}
              </ScrollableOverlay>
            </div>
          )}

          {/* Web runtime fallback command */}
          {isWebRuntime && webUpdateState === 'error' && (
            <div className="space-y-2 mt-4">
              <div className="flex items-center gap-2 typography-meta text-muted-foreground">
                <Icon name="terminal" className="h-4 w-4" />
                <span>{t('updateDialog.fallback.updateViaTerminal')}</span>
              </div>
              <div className="flex items-center gap-2 p-1 pl-3 bg-[var(--surface-elevated)]/50 rounded-md border border-[var(--surface-subtle)]">
                <code className="flex-1 font-mono text-sm text-foreground overflow-x-auto whitespace-nowrap">
                  {updateCommand}
                </code>
                <button
                  onClick={handleCopyCommand}
                  className={cn(
                    'flex items-center justify-center p-2 rounded',
                    'text-muted-foreground hover:text-foreground hover:bg-[var(--interactive-hover)]',
                    'transition-colors',
                    copied && 'text-[var(--status-success)]'
                  )}
                  title={copied ? t('updateDialog.actions.copied') : t('updateDialog.actions.copyCommand')}
                >
                  {copied ? (
                    <Icon name="check" className="h-4 w-4" />
                  ) : (
                    <Icon name="clipboard" className="h-4 w-4" />
                  )}
                </button>
              </div>
            </div>
          )}

          {/* Desktop progress bar */}
          {!isWebRuntime && downloading && (
            <div className="space-y-2 mt-4">
              <div className="flex items-center justify-between text-sm">
                <span className="text-muted-foreground">{t('updateDialog.status.downloadingPayload')}</span>
                <span className="font-mono text-foreground">{progressPercent}%</span>
              </div>
              <div className="h-1.5 bg-[var(--surface-subtle)] rounded-full overflow-hidden">
                <div
                  className="h-full bg-[var(--primary-base)] transition-all duration-300"
                  style={{ width: `${progressPercent}%` }}
                />
              </div>
            </div>
          )}

          {/* Error display */}
          {(error || webError) && (
            <div className="p-3 mt-4 bg-[var(--status-error-background)] border border-[var(--status-error-border)] rounded-lg">
              <p className="text-sm text-[var(--status-error)]">{error || webError}</p>
            </div>
          )}
        </div>

        {/* Action Footer */}
        <div className="mt-4 flex items-center justify-between gap-4">
          <a
            href={releaseUrl}
            target="_blank"
            rel="noopener noreferrer"
            className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors shrink-0"
          >
            <Icon name="external-link" className="h-4 w-4" />
            GitHub
          </a>

          <div className="flex-1 flex justify-end">
            {/* Desktop Buttons */}
            {!isWebRuntime && !downloaded && !downloading && (
              <button
                onClick={onDownload}
                className="flex items-center justify-center gap-2 px-5 py-2 rounded-md text-sm font-medium bg-[var(--primary-base)] text-[var(--primary-foreground)] hover:opacity-90 transition-opacity"
              >
                <Icon name="download" className="h-4 w-4" />
                {t('updateDialog.actions.downloadUpdate')}
              </button>
            )}

            {!isWebRuntime && downloading && (
              <button
                disabled
                className="flex items-center justify-center gap-2 px-5 py-2 rounded-md text-sm font-medium bg-[var(--primary-base)]/50 text-[var(--primary-foreground)] cursor-not-allowed"
              >
                <Icon name="loader" className="h-4 w-4 animate-spin" />
                {t('updateDialog.status.downloading')}
              </button>
            )}

            {!isWebRuntime && downloaded && (
              <button
                onClick={onRestart}
                className="flex items-center justify-center gap-2 px-5 py-2 rounded-md text-sm font-medium bg-[var(--status-success)] text-white hover:opacity-90 transition-opacity"
              >
                <Icon name="restart" className="h-4 w-4" />
                {t('updateDialog.actions.restartToUpdate')}
              </button>
            )}

            {/* Web Buttons */}
            {isWebRuntime && !isWebUpdating && (
              <button
                onClick={handleWebUpdate}
                className="flex items-center justify-center gap-2 px-5 py-2 rounded-md text-sm font-medium bg-[var(--primary-base)] text-[var(--primary-foreground)] hover:opacity-90 transition-opacity"
              >
                <Icon name="download" className="h-4 w-4" />
                {t('updateDialog.actions.updateNow')}
              </button>
            )}

            {isWebRuntime && isWebUpdating && (
              <button
                disabled
                className="flex items-center justify-center gap-2 px-5 py-2 rounded-md text-sm font-medium bg-[var(--primary-base)]/50 text-[var(--primary-foreground)] cursor-not-allowed"
              >
                <Icon name="loader" className="h-4 w-4 animate-spin" />
                {t('updateDialog.status.updating')}
              </button>
            )}
          </div>
        </div>
      </DialogContent>
    </Dialog>
  );
};
