/**
 * 모바일·PC 프로그램 설치 — 탭 UI (모바일 QR / PC dmg·exe + Jenkins 빌드)
 */
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import QRCode from 'qrcode';
import DraggableModalFrame from './DraggableModalFrame';
import {
  loadDesktopAppBuildStatus,
  loadDesktopAppReleaseInfo,
  loadMobileAppReleaseInfo,
  triggerDesktopAppBuild,
  type DesktopAppBuildStatusDto,
  type DesktopAppPlatformReleaseDto,
  type DesktopAppReleaseInfoDto,
  type MobileAppReleaseInfoDto,
} from '../utils/backendApi';

interface Props {
  onClose: () => void;
  /** ADMIN — Jenkins 데스크톱 빌드 트리거 */
  canBuildDesktop?: boolean;
}

type TabId = 'mobile' | 'desktop';

function formatBytes(n?: number): string {
  if (n == null || !Number.isFinite(n)) return '—';
  if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
  return `${(n / (1024 * 1024)).toFixed(1)} MB`;
}

function resolveDownloadUrl(info: MobileAppReleaseInfoDto | null): string {
  if (!info?.available) return '';
  return info.downloadUrl?.trim() ?? '';
}

function triggerDownload(url: string, fileName: string) {
  const a = document.createElement('a');
  a.href = url;
  a.download = fileName;
  a.rel = 'noopener';
  document.body.appendChild(a);
  a.click();
  a.remove();
}

const IcDesktopBuild = () => (
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
    <path d="M12 3v4" />
    <path d="M8 7h8" />
    <rect x="5" y="7" width="14" height="12" rx="2" />
    <path d="M9 13h6" />
    <path d="M9 17h4" />
  </svg>
);

const MobileTab: React.FC = () => {
  const [info, setInfo] = useState<MobileAppReleaseInfoDto | null>(null);
  const [loading, setLoading] = useState(true);
  const [qrDataUrl, setQrDataUrl] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  const downloadUrl = useMemo(() => resolveDownloadUrl(info), [info]);

  useEffect(() => {
    let cancelled = false;
    void loadMobileAppReleaseInfo()
      .then(data => { if (!cancelled) setInfo(data); })
      .catch(() => { if (!cancelled) setError('앱 정보를 불러오지 못했습니다.'); })
      .finally(() => { if (!cancelled) setLoading(false); });
    return () => { cancelled = true; };
  }, []);

  useEffect(() => {
    if (!downloadUrl) {
      setQrDataUrl(null);
      return;
    }
    let cancelled = false;
    void QRCode.toDataURL(downloadUrl, {
      width: 220,
      margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    }).then(url => { if (!cancelled) setQrDataUrl(url); })
      .catch(() => { if (!cancelled) setQrDataUrl(null); });
    return () => { cancelled = true; };
  }, [downloadUrl]);

  if (loading) return <p className="app-download-muted">앱 정보 불러오는 중…</p>;
  if (error) return <p className="app-download-error">{error}</p>;
  if (!info?.available) {
    return (
      <div className="app-download-empty">
        <div className="app-download-empty-icon" aria-hidden>📱</div>
        <h3>설치 파일 준비 중</h3>
        <p>
          Android APK가 아직 서버에 업로드되지 않았습니다.
          <br />
          <code>data/mobile-releases/goldenchart-android.apk</code> 경로에 빌드 파일을 배치하세요.
        </p>
      </div>
    );
  }

  return (
    <>
      <div className="app-download-hero">
        <div className="app-download-app-icon" aria-hidden>GC</div>
        <div>
          <h3 className="app-download-app-name">GoldenChart Android</h3>
          <p className="app-download-meta">
            {info.version && <span>v{info.version}</span>}
            {info.sizeBytes != null && <span>{formatBytes(info.sizeBytes)}</span>}
            {info.updatedAt && <span>{info.updatedAt}</span>}
          </p>
          <p className="app-download-muted" style={{ marginTop: 4, fontSize: 12 }}>
            휴대폰·태블릿(패드) 동일 APK
          </p>
        </div>
      </div>

      <div className="app-download-qr-block">
        {qrDataUrl ? (
          <img src={qrDataUrl} alt="앱 설치 QR 코드" className="app-download-qr" width={220} height={220} />
        ) : (
          <div className="app-download-qr app-download-qr--placeholder">QR 생성 중…</div>
        )}
        <div className="app-download-qr-help">
          <strong>휴대폰·태블릿 카메라로 QR 스캔</strong>
          <p>Android에서 APK 다운로드 페이지가 열립니다. 다운로드 후 설치하세요.</p>
          <p className="app-download-muted app-download-url" title={downloadUrl}>{downloadUrl}</p>
        </div>
      </div>

      <button
        type="button"
        className="app-download-btn-primary"
        onClick={() => triggerDownload(downloadUrl, info.fileName ?? 'goldenchart-android.apk')}
      >
        Android APK 다운로드
      </button>

      <details className="app-download-steps">
        <summary>설치 방법 (Android · 패드·폰)</summary>
        <ol>
          <li>QR 코드를 스캔하거나 위 버튼으로 APK를 다운로드합니다.</li>
          <li>「출처를 알 수 없는 앱」 설치 허용이 필요할 수 있습니다.</li>
          <li>다운로드 완료 후 APK 파일을 탭하여 설치합니다.</li>
          <li>앱 실행 후 설정 → 「업데이트 확인」으로 새 버전을 받을 수 있습니다.</li>
        </ol>
      </details>
    </>
  );
};

const PlatformCard: React.FC<{
  platform: 'mac' | 'windows' | 'android';
  release?: DesktopAppPlatformReleaseDto;
  mobile?: MobileAppReleaseInfoDto | null;
}> = ({ platform, release, mobile }) => {
  const configs = {
    mac: { label: 'macOS (Apple Silicon · Intel)', icon: '🍎', ext: '.dmg', isLink: false },
    windows: { label: 'Windows (64-bit)', icon: '🪟', ext: '.exe', isLink: false },
    android: { label: 'Android (패드 · 폰 · 별도 버전)', icon: '🤖', ext: '.apk', isLink: false },
  } as const;
  const { label, icon, ext } = configs[platform];

  if (platform === 'android') {
    if (!mobile?.available || !mobile.downloadUrl) {
      return (
        <div className="app-download-pc-card app-download-pc-card--empty">
          <div className="app-download-pc-icon" aria-hidden>{icon}</div>
          <div>
            <strong>{label}</strong>
            <p className="app-download-muted">설치 파일 준비 중 ({ext})</p>
          </div>
        </div>
      );
    }
    return (
      <div className="app-download-pc-card">
        <div className="app-download-pc-icon" aria-hidden>{icon}</div>
        <div className="app-download-pc-body">
          <strong>{label}</strong>
          <p className="app-download-meta">
            {mobile.version && <span>v{mobile.version}</span>}
            {mobile.sizeBytes != null && <span>{formatBytes(mobile.sizeBytes)}</span>}
            {mobile.updatedAt && <span>{mobile.updatedAt}</span>}
          </p>
          <p className="app-download-muted app-download-url" title={mobile.downloadUrl}>
            {mobile.fileName ?? `goldenchart-android${ext}`}
          </p>
        </div>
        <button
          type="button"
          className="app-download-pc-btn"
          onClick={() => triggerDownload(mobile.downloadUrl!, mobile.fileName ?? `goldenchart-android${ext}`)}
        >
          다운로드
        </button>
      </div>
    );
  }

  if (!release?.available || !release.downloadUrl) {
    return (
      <div className="app-download-pc-card app-download-pc-card--empty">
        <div className="app-download-pc-icon" aria-hidden>{icon}</div>
        <div>
          <strong>{label}</strong>
          <p className="app-download-muted">설치 파일 준비 중 ({ext})</p>
        </div>
      </div>
    );
  }

  return (
    <div className="app-download-pc-card">
      <div className="app-download-pc-icon" aria-hidden>{icon}</div>
      <div className="app-download-pc-body">
        <strong>{label}</strong>
        <p className="app-download-meta">
          {release.version && <span>v{release.version}</span>}
          {release.sizeBytes != null && <span>{formatBytes(release.sizeBytes)}</span>}
          {release.updatedAt && <span>{release.updatedAt}</span>}
        </p>
        <p className="app-download-muted app-download-url" title={release.downloadUrl}>
          {release.fileName}
        </p>
      </div>
      <button
        type="button"
        className="app-download-pc-btn"
        onClick={() => triggerDownload(release.downloadUrl!, release.fileName ?? `GoldenChart${ext}`)}
      >
        다운로드
      </button>
    </div>
  );
};

const BUILD_POLL_MS = 4000;

function formatElapsed(startedAtMs?: number): string {
  if (startedAtMs == null || !Number.isFinite(startedAtMs)) return '—';
  const sec = Math.max(0, Math.floor((Date.now() - startedAtMs) / 1000));
  const m = Math.floor(sec / 60);
  const s = sec % 60;
  return m > 0 ? `${m}분 ${s.toString().padStart(2, '0')}초` : `${s}초`;
}

function buildStatusLabel(status?: string, building?: boolean): string {
  if (building) return '빌드 진행 중';
  switch (status) {
    case 'building': return '빌드 진행 중';
    case 'queued': return 'Jenkins 큐 대기';
    case 'success': return '빌드 성공';
    case 'failure': return '빌드 실패';
    case 'aborted': return '빌드 중단';
    case 'error': return '상태 조회 오류';
    case 'disabled': return 'Jenkins 비활성';
    case 'ready': return '빌드 준비됨';
    case 'idle': return '대기 중';
    default: return status ?? '—';
  }
}

function isTerminalBuildStatus(status?: string, building?: boolean): boolean {
  if (building) return false;
  return status === 'success' || status === 'failure' || status === 'aborted';
}

const DesktopBuildProgressPanel: React.FC<{
  status: DesktopAppBuildStatusDto | null;
  hint: string | null;
  busy: boolean;
  tracking: boolean;
  tick: number;
}> = ({ status, hint, busy, tracking, tick }) => {
  void tick;
  const building = Boolean(status?.building || status?.status === 'queued');
  const active = busy || building || tracking;
  const terminal = isTerminalBuildStatus(status?.status, status?.building);
  const failed = !building && (status?.status === 'failure' || status?.status === 'error');
  const succeeded = !building && status?.status === 'success';

  if (!active && !terminal && !hint) return null;

  const steps = [
    { id: 'queue', label: 'Jenkins 빌드 시작', done: Boolean(status?.buildNumber) || building || terminal },
    {
      id: 'compile',
      label: 'macOS dmg · Windows exe 빌드',
      done: succeeded || (building && Boolean(status?.buildNumber)),
      active: building && Boolean(status?.buildNumber),
    },
    { id: 'deploy', label: '설치 파일 배포', done: succeeded, active: false },
  ];

  return (
    <div
      className={`app-download-build-progress${active ? ' app-download-build-progress--active' : ''}${failed ? ' app-download-build-progress--failed' : ''}${succeeded ? ' app-download-build-progress--success' : ''}`}
      role="status"
      aria-live="polite"
    >
      <div className="app-download-build-progress-head">
        <div className="app-download-build-progress-title">
          {(active || building) && <span className="app-download-build-spinner" aria-hidden />}
          <strong>{buildStatusLabel(status?.status, status?.building)}</strong>
          {status?.buildNumber != null && (
            <span className="app-download-build-badge">#{status.buildNumber}</span>
          )}
        </div>
        {(building || status?.startedAtMs) && (
          <span className="app-download-build-elapsed">
            경과 {formatElapsed(status?.startedAtMs)}
          </span>
        )}
      </div>

      {(active || building) && (
        <div className="app-download-build-bar" aria-hidden>
          <div className="app-download-build-bar-fill" />
        </div>
      )}

      <ol className="app-download-build-steps">
        {steps.map(step => (
          <li
            key={step.id}
            className={`app-download-build-step${step.done ? ' done' : ''}${step.active ? ' active' : ''}`}
          >
            <span className="app-download-build-step-dot" aria-hidden />
            {step.label}
          </li>
        ))}
      </ol>

      {(hint || status?.message) && (
        <p className="app-download-build-message">{hint ?? status?.message}</p>
      )}

      {succeeded && (
        <p className="app-download-build-done-note">아래에서 macOS·Windows 설치 파일을 다운로드할 수 있습니다.</p>
      )}
    </div>
  );
};

const DesktopTab: React.FC<{ canBuildDesktop: boolean }> = ({ canBuildDesktop }) => {
  const [info, setInfo] = useState<DesktopAppReleaseInfoDto | null>(null);
  const [mobileInfo, setMobileInfo] = useState<MobileAppReleaseInfoDto | null>(null);
  const [buildStatus, setBuildStatus] = useState<DesktopAppBuildStatusDto | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [buildBusy, setBuildBusy] = useState(false);
  const [buildHint, setBuildHint] = useState<string | null>(null);
  const [buildTracking, setBuildTracking] = useState(false);
  const [elapsedTick, setElapsedTick] = useState(0);

  const reloadRelease = useCallback(async () => {
    const [desktop, mobile] = await Promise.all([
      loadDesktopAppReleaseInfo(),
      loadMobileAppReleaseInfo(),
    ]);
    setInfo(desktop);
    setMobileInfo(mobile);
    return desktop;
  }, []);

  const reloadBuildStatus = useCallback(async () => {
    const st = await loadDesktopAppBuildStatus();
    setBuildStatus(st);
    return st;
  }, []);

  useEffect(() => {
    let cancelled = false;
    void Promise.all([reloadRelease(), reloadBuildStatus()])
      .catch(() => { if (!cancelled) setError('PC 프로그램 정보를 불러오지 못했습니다.'); })
      .finally(() => { if (!cancelled) setLoading(false); });
    return () => { cancelled = true; };
  }, [reloadRelease, reloadBuildStatus]);

  const isBuilding = Boolean(buildStatus?.building || buildStatus?.status === 'queued');
  const showBuildProgress = Boolean(
    buildBusy || buildTracking || isBuilding
    || (buildStatus?.buildNumber && buildStatus.status !== 'idle' && buildStatus.status !== 'ready'),
  );

  useEffect(() => {
    if (!showBuildProgress || buildStatus?.startedAtMs == null) return;
    const timer = window.setInterval(() => setElapsedTick(t => t + 1), 1000);
    return () => window.clearInterval(timer);
  }, [showBuildProgress, buildStatus?.startedAtMs]);

  useEffect(() => {
    if (!buildTracking && !isBuilding && !buildBusy) return;
    const poll = () => {
      void reloadBuildStatus().then(st => {
        if (!st) return;
        if (isTerminalBuildStatus(st.status, st.building)) {
          setBuildTracking(false);
          void reloadRelease();
        } else if (!st.building && st.status !== 'queued') {
          void reloadRelease();
        }
      });
    };
    poll();
    const timer = window.setInterval(poll, BUILD_POLL_MS);
    return () => window.clearInterval(timer);
  }, [buildTracking, isBuilding, buildBusy, reloadBuildStatus, reloadRelease]);

  const handleBuild = useCallback(async () => {
    if (!canBuildDesktop || buildBusy || isBuilding) return;
    setBuildBusy(true);
    setBuildTracking(true);
    setBuildHint('Jenkins 빌드 요청 중…');
    try {
      const res = await triggerDesktopAppBuild();
      setBuildHint(res.message ?? (res.accepted ? '빌드 시작됨' : '빌드 요청 거부'));
      if (!res.accepted) setBuildTracking(false);
      await reloadBuildStatus();
      if (res.accepted) {
        window.setTimeout(() => { void reloadBuildStatus(); }, 2000);
      }
    } catch (e) {
      setBuildHint(e instanceof Error ? e.message : '빌드 요청 실패');
      setBuildTracking(false);
    } finally {
      setBuildBusy(false);
    }
  }, [canBuildDesktop, buildBusy, isBuilding, reloadBuildStatus]);

  const hasAny = info?.mac?.available || info?.windows?.available || mobileInfo?.available;

  const desktopVersionMismatch = Boolean(
    info?.version
    && (
      (info.mac?.available && info.mac.version && info.mac.version !== info.version)
      || (info.windows?.available && info.windows.version && info.windows.version !== info.version)
    ),
  );

  if (loading) return <p className="app-download-muted">PC 프로그램 정보 불러오는 중…</p>;
  if (error) return <p className="app-download-error">{error}</p>;

  const statusMessage = !showBuildProgress ? (buildHint ?? buildStatus?.message) : null;

  return (
    <>
      <div className="app-download-hero app-download-hero--with-action">
        <div className="app-download-hero-main">
          <div className="app-download-app-icon app-download-app-icon--desktop" aria-hidden>GC</div>
          <div>
            <h3 className="app-download-app-name">GoldenChart Desktop</h3>
            <p className="app-download-meta">
              {info?.version && <span>v{info.version}</span>}
              {info?.updatedAt && <span>{info.updatedAt}</span>}
            </p>
          </div>
        </div>
        {canBuildDesktop && buildStatus?.jenkinsEnabled !== false && (
          <button
            type="button"
            className={`app-download-build-btn${buildBusy || isBuilding ? ' busy' : ''}`}
            onClick={() => { void handleBuild(); }}
            disabled={buildBusy || isBuilding}
            title="Git main 최신 소스로 macOS·Windows 데스크톱 앱 Jenkins 빌드"
            aria-label="데스크톱 앱 빌드"
          >
            <IcDesktopBuild />
            <span>{isBuilding ? '빌드 중…' : '앱 빌드'}</span>
          </button>
        )}
      </div>

      {desktopVersionMismatch && (
        <p className="app-download-error" style={{ marginBottom: 8 }}>
          macOS·Windows 설치 파일 버전이 최신 빌드(v{info?.version})와 다릅니다. 「앱 빌드」를 다시 실행하세요.
        </p>
      )}

      {showBuildProgress && (
        <DesktopBuildProgressPanel
          status={buildStatus}
          hint={buildHint}
          busy={buildBusy}
          tracking={buildTracking}
          tick={elapsedTick}
        />
      )}

      {statusMessage && (
        <p className={`app-download-build-status${isBuilding ? ' app-download-build-status--active' : ''}`}>
          {statusMessage}
        </p>
      )}

      {!canBuildDesktop && (
        <p className="app-download-muted app-download-build-note">
          데스크톱 앱 빌드는 <strong>관리자</strong> 로그인 후 사용할 수 있습니다.
        </p>
      )}

      {!hasAny && !isBuilding && (
        <div className="app-download-empty">
          <div className="app-download-empty-icon" aria-hidden>💻</div>
          <h3>설치 파일 준비 중</h3>
          <p>
            macOS(.dmg), Windows(.exe) 또는 Android(.apk)가 아직 준비되지 않았습니다.
            <br />
            {canBuildDesktop
              ? '우측 상단 「앱 빌드」 버튼으로 Jenkins 빌드를 시작하세요.'
              : '관리자에게 Jenkins 빌드를 요청하세요.'}
          </p>
        </div>
      )}

      {isBuilding && !hasAny && (
        <p className="app-download-muted app-download-build-wait">
          Tauri macOS dmg · Windows NSIS 빌드가 진행 중입니다. 완료되면 아래에 다운로드가 표시됩니다.
        </p>
      )}

      {(hasAny || isBuilding) && (
        <div className="app-download-pc-list">
          <PlatformCard platform="mac" release={info?.mac} />
          <PlatformCard platform="windows" release={info?.windows} />
          <PlatformCard platform="android" mobile={mobileInfo} />
        </div>
      )}

      {(hasAny || isBuilding) && (
        <details className="app-download-steps">
          <summary>설치 방법</summary>
          <ol>
            <li><strong>macOS</strong>: dmg를 열고 GoldenChart.app을 Applications 폴더로 드래그합니다.</li>
            <li>
              <strong>macOS — 「손상되었기 때문에 열 수 없습니다」</strong> 가 뜨면 앱이 깨진 것이 아니라
              macOS Gatekeeper가 미公証 앱을 차단한 것입니다. 먼저 <strong>취소</strong>를 누른 뒤
              Finder → 응용 프로그램에서 GoldenChart를 <strong>우클릭 → 열기</strong> 하거나,
              시스템 설정 → 개인정보 보호 및 보안 → <strong>그래도 열기</strong> 를 선택하세요.
              그래도 안 되면 터미널에서{' '}
              <code>xattr -cr /Applications/GoldenChart.app</code> 실행 후 다시 열어보세요.
            </li>
            <li><strong>Windows</strong>: setup.exe를 실행하고 안내에 따라 설치합니다.</li>
            <li><strong>Android (패드·폰)</strong>: APK를 다운로드한 뒤 설치합니다. 패드와 폰은 동일 APK입니다.</li>
            <li>설치된 PC 앱은 메뉴 우측 상단 <strong>업데이트</strong> 버튼으로 자동 업데이트할 수 있습니다.</li>
            <li>Android 앱은 시작·복귀 시 또는 설정 → <strong>업데이트 확인</strong>으로 새 APK를 안내합니다.</li>
            <li>빌드는 Git <code>main</code> 최신 커밋 기준이며, 완료 후 설치 파일이 자동으로 배포됩니다.</li>
          </ol>
        </details>
      )}
    </>
  );
};

const AppDownloadModal: React.FC<Props> = ({ onClose, canBuildDesktop = false }) => {
  const [tab, setTab] = useState<TabId>('mobile');

  return (
    <DraggableModalFrame
      onClose={onClose}
      title="Apps"
      titleKo="GoldenChart 앱·PC 프로그램"
      badge={tab === 'mobile' ? 'Mobile' : 'Desktop'}
      width={480}
      dialogClassName="sp-modal app-download-modal"
    >
      <div className="app-download-tabs" role="tablist" aria-label="다운로드 종류">
        <button
          type="button"
          role="tab"
          aria-selected={tab === 'mobile'}
          className={`app-download-tab${tab === 'mobile' ? ' active' : ''}`}
          onClick={() => setTab('mobile')}
        >
          모바일
        </button>
        <button
          type="button"
          role="tab"
          aria-selected={tab === 'desktop'}
          className={`app-download-tab${tab === 'desktop' ? ' active' : ''}`}
          onClick={() => setTab('desktop')}
        >
          PC 프로그램
        </button>
      </div>

      <div className="app-download-body" role="tabpanel">
        {tab === 'mobile' ? <MobileTab /> : <DesktopTab canBuildDesktop={canBuildDesktop} />}
      </div>
    </DraggableModalFrame>
  );
};

export default AppDownloadModal;
