import React, { useEffect, useMemo, useState } from "react";
import { createJob, deleteUpload, getHealth, getJob, getReport, stageMailboxZip } from "./api.js";

const APP_NAME = import.meta.env.VITE_APP_NAME || "Mail Summary";
const APP_VERSION = import.meta.env.VITE_APP_VERSION || "1.0.0";

const STATUS_LABEL = {
  pending: "대기 중",
  extracting: "준비 중",
  filtering: "메일 스캔 / 기간 필터",
  analyzing: "LLM 분석",
  completed: "완료",
  failed: "실패",
};

const ANALYSIS_STEPS = [
  { key: "extracting", label: "1. 준비" },
  { key: "filtering", label: "2. 메일 스캔·기간 필터" },
  { key: "analyzing", label: "3. LLM 요약·보고 작성" },
  { key: "completed", label: "4. 완료" },
];

function stepState(currentStatus, key) {
  const order = ["pending", "extracting", "filtering", "analyzing", "completed"];
  if (currentStatus === "failed") {
    return key === "extracting" || key === "filtering" || key === "analyzing" ? "done" : "wait";
  }
  const cur = order.indexOf(currentStatus);
  const idx = order.indexOf(key);
  if (cur < 0) return "wait";
  if (idx < cur) return "done";
  if (idx === cur) return "active";
  return "wait";
}

function todayIso() {
  return new Date().toISOString().slice(0, 10);
}

function weekAgoIso() {
  const d = new Date();
  d.setDate(d.getDate() - 6);
  return d.toISOString().slice(0, 10);
}

function scopeLabel(scope) {
  if (scope === "inbox") return "받은 메일함만";
  if (scope === "sent") return "보낸 메일함만";
  return "받은 + 보낸 메일함";
}

function emptySlot() {
  return { file: null, uploadId: "", progress: 0, status: "idle", error: "" };
  // status: idle | uploading | done | error
}

function StreamingText({ text, active }) {
  const [shown, setShown] = useState("");

  useEffect(() => {
    if (!active || !text) {
      setShown(text || "");
      return undefined;
    }
    setShown("");
    let i = 0;
    const step = Math.max(1, Math.ceil(text.length / 80));
    const timer = setInterval(() => {
      i = Math.min(text.length, i + step);
      setShown(text.slice(0, i));
      if (i >= text.length) clearInterval(timer);
    }, 28);
    return () => clearInterval(timer);
  }, [text, active]);

  return (
    <p className="stream-text">
      {shown}
      {active && shown.length < (text || "").length ? <span className="caret" /> : null}
    </p>
  );
}

function formatSideText(groups) {
  if (!groups?.length) return "(해당 항목 없음)";
  return groups
    .map((group) => {
      const customer = group?.customer || "기타";
      const items = Array.isArray(group?.items) ? group.items.filter(Boolean) : [];
      const bullets = items.length ? items.map((item) => `- ${item}`).join("\n") : "- (세부 항목 없음)";
      return `${customer}\n${bullets}`;
    })
    .join("\n\n");
}

function WeeklySide({ title, groups, stream }) {
  const text = formatSideText(groups);
  return (
    <div className="weekly-col">
      <h3>{title}</h3>
      <div className="weekly-body">
        <StreamingText text={text} active={stream} />
      </div>
    </div>
  );
}

function CopyIcon() {
  return (
    <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
      <rect x="9" y="9" width="11" height="11" rx="1.5" fill="none" stroke="currentColor" strokeWidth="1.8" />
      <path
        d="M6 15H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v1"
        fill="none"
        stroke="currentColor"
        strokeWidth="1.8"
      />
    </svg>
  );
}

function CheckIcon() {
  return (
    <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
      <path
        d="M5 12.5 10 17.5 19 7"
        fill="none"
        stroke="currentColor"
        strokeWidth="2.2"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

function UploadSlot({ title, slot, onPick, onClear }) {
  const showBar = slot.status === "uploading" || slot.status === "error";
  const statusText =
    slot.status === "uploading"
      ? `업로드 중… ${slot.progress}%`
      : slot.status === "error"
        ? slot.error || "업로드 실패"
        : "";

  return (
    <div className="drop-block">
      <div className={`drop-row ${slot.file ? "has-file" : ""}`}>
        <label className={`drop ${slot.file ? "has-file" : ""}`}>
          <span>{title}</span>
          <p className="file-name">{slot.file ? slot.file.name : "클릭해서 zip 선택"}</p>
          <input
            type="file"
            accept=".zip,application/zip"
            disabled={slot.status === "uploading"}
            onChange={(e) => onPick(e.target.files?.[0] || null)}
          />
        </label>
        {slot.file && slot.status !== "uploading" ? (
          <button
            type="button"
            className="clear-upload"
            aria-label={`${title} 업로드 삭제`}
            onClick={onClear}
          >
            ×
          </button>
        ) : null}
        {showBar ? (
          <div className="upload-bar" aria-hidden="true">
            <i style={{ width: `${slot.progress}%` }} />
          </div>
        ) : null}
        {showBar && statusText ? (
          <p className={`upload-status ${slot.status === "error" ? "bad" : ""}`}>{statusText}</p>
        ) : null}
      </div>
    </div>
  );
}

export default function App() {
  const [step, setStep] = useState(1);
  const [unlockedStep, setUnlockedStep] = useState(1);
  const [health, setHealth] = useState(null);
  const [inbox, setInbox] = useState(emptySlot);
  const [sent, setSent] = useState(emptySlot);
  const [dateFrom, setDateFrom] = useState(weekAgoIso);
  const [dateTo, setDateTo] = useState(todayIso);
  const [mailboxScope, setMailboxScope] = useState("both");
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState("");
  const [job, setJob] = useState(null);
  const [report, setReport] = useState(null);
  const [streamSummary, setStreamSummary] = useState(false);
  const [copied, setCopied] = useState(false);

  function goToStep(next) {
    if (next > unlockedStep) return;
    if (next === 2) setMailboxScope(inferMailboxScope());
    setStep(next);
  }

  async function goHome() {
    // Soft state reset felt different from a real refresh — hard-navigate home instead.
    const ids = [inbox.uploadId, sent.uploadId].filter(Boolean);
    if (ids.length) {
      await Promise.allSettled(ids.map((id) => deleteUpload(id)));
    }
    window.location.assign("/");
  }

  function goBackToConditions() {
    setJob(null);
    setReport(null);
    setError("");
    setBusy(false);
    setStreamSummary(false);
    setCopied(false);
    setUnlockedStep(2);
    setMailboxScope(inferMailboxScope());
    setStep(2);
  }

  const inboxReady = inbox.status === "done" && Boolean(inbox.uploadId);
  const sentReady = sent.status === "done" && Boolean(sent.uploadId);
  const uploading = inbox.status === "uploading" || sent.status === "uploading";
  const canGoNextFromUpload = (inboxReady || sentReady) && !uploading;

  function inferMailboxScope() {
    if (inboxReady && sentReady) return "both";
    if (inboxReady) return "inbox";
    if (sentReady) return "sent";
    return "both";
  }

  function goNextFromUpload() {
    if (!canGoNextFromUpload) return;
    setUnlockedStep((prev) => Math.max(prev, 2));
    setMailboxScope(inferMailboxScope());
    setStep(2);
  }

  async function clearSlot(mailbox) {
    const slot = mailbox === "inbox" ? inbox : sent;
    const setSlot = mailbox === "inbox" ? setInbox : setSent;
    if (slot.uploadId) {
      try {
        await deleteUpload(slot.uploadId);
      } catch {
        // Local reset still proceeds even if server cleanup fails.
      }
    }
    setSlot(emptySlot());
    setUnlockedStep(1);
    if (step !== 1) setStep(1);
  }

  async function handlePick(mailbox, file) {
    const setSlot = mailbox === "inbox" ? setInbox : setSent;
    if (!file) {
      setSlot(emptySlot());
      return;
    }
    if (!file.name.toLowerCase().endsWith(".zip")) {
      setSlot({
        file,
        uploadId: "",
        progress: 0,
        status: "error",
        error: "zip 파일만 업로드할 수 있습니다.",
      });
      return;
    }

    setError("");
    setSlot({ file, uploadId: "", progress: 0, status: "uploading", error: "" });
    setUnlockedStep(1);
    if (step !== 1) setStep(1);

    try {
      const result = await stageMailboxZip(file, mailbox, (pct) => {
        setSlot((prev) => ({ ...prev, progress: pct, status: "uploading" }));
      });
      setSlot({
        file,
        uploadId: result.upload_id,
        progress: 100,
        status: "done",
        error: "",
      });
    } catch (err) {
      setSlot({
        file,
        uploadId: "",
        progress: 0,
        status: "error",
        error: err.message || "업로드 실패",
      });
    }
  }

  useEffect(() => {
    getHealth()
      .then(setHealth)
      .catch(() => setHealth({ status: "error", database: "down", llm: { ok: false } }));
  }, []);

  useEffect(() => {
    if (!job?.id) return undefined;
    if (job.status === "completed" || job.status === "failed") return undefined;

    let cancelled = false;
    const tick = async () => {
      try {
        const payload = await getJob(job.id);
        if (cancelled) return;
        setJob(payload.job);
        if (payload.job.status === "failed") {
          setError(payload.job.error_message || "분석에 실패했습니다.");
        }
      } catch (err) {
        if (!cancelled) setError(err.message || "상태 조회 실패");
      }
    };

    tick();
    const timer = setInterval(tick, 1500);
    return () => {
      cancelled = true;
      clearInterval(timer);
    };
  }, [job?.id, job?.status]);

  // Fetch report in a separate effect so polling cleanup can't drop the result
  // when status flips to completed mid-request.
  useEffect(() => {
    if (!job?.id || job.status !== "completed" || report) return undefined;

    let cancelled = false;
    getReport(job.id)
      .then((payload) => {
        if (cancelled) return;
        setReport(payload.report);
        setStreamSummary(true);
      })
      .catch((err) => {
        if (!cancelled) setError(err.message || "결과 조회 실패");
      });
    return () => {
      cancelled = true;
    };
  }, [job?.id, job?.status, report]);

  const canAnalyze = useMemo(() => {
    if (!dateFrom || !dateTo || dateFrom > dateTo) return false;
    if (mailboxScope === "inbox") return inboxReady;
    if (mailboxScope === "sent") return sentReady;
    return inboxReady && sentReady;
  }, [dateFrom, dateTo, mailboxScope, inboxReady, sentReady]);

  async function handleStart() {
    setError("");
    setBusy(true);
    setReport(null);
    setStreamSummary(false);
    try {
      const form = new FormData();
      form.append("date_from", dateFrom);
      form.append("date_to", dateTo);
      form.append("mailbox_scope", mailboxScope);
      if (inbox.uploadId) form.append("inbox_upload_id", inbox.uploadId);
      if (sent.uploadId) form.append("sent_upload_id", sent.uploadId);

      const created = await createJob(form);
      setJob(created.job);
      setUnlockedStep(3);
      setStep(3);
    } catch (err) {
      setError(err.message || "분석 요청에 실패했습니다.");
    } finally {
      setBusy(false);
    }
  }

  async function copyReport() {
    const text = report?.report_markdown?.trim();
    if (!text) return;
    try {
      await navigator.clipboard.writeText(text);
      setCopied(true);
      window.setTimeout(() => setCopied(false), 2000);
    } catch {
      setError("클립보드 복사에 실패했습니다.");
    }
  }

  const data = report?.report || {};
  const running = job && !["completed", "failed"].includes(job.status);

  return (
    <div className="shell">
      <header className="topbar">
        <button type="button" className="brand-mark brand-link" onClick={goHome}>
          <strong>{APP_NAME}</strong>
        </button>
        <div className="live">
          <span className={`live-item ${health?.database === "up" ? "ok" : "bad"}`}>
            <i />
            DB
          </span>
          <span className={`live-item ${health?.llm?.ok ? "ok" : "bad"}`}>
            <i />
            LLM(on-prem · 30B)
          </span>
          <span className="live-item">v{APP_VERSION}</span>
        </div>
      </header>

      <section className="hero">
        <h1>
          한 주의 메일을 <em>업무보고로</em>
        </h1>
        <p className="hero-lead">
          메일을 외부로 전송하지 않고, 로컬에서 분석해 주간 업무를 자동으로 정리합니다.
        </p>
      </section>

      <div className="layout">
        <nav className="steps" aria-label="진행 단계">
          <button type="button" className={step === 1 ? "active" : ""} onClick={() => goToStep(1)}>
            01 업로드
            <small>mailbox zip</small>
          </button>
          <button
            type="button"
            className={step === 2 ? "active" : ""}
            onClick={() => goToStep(2)}
            disabled={unlockedStep < 2}
          >
            02 조건
            <small>range & scope</small>
          </button>
          <button
            type="button"
            className={step === 3 ? "active" : ""}
            onClick={() => goToStep(3)}
            disabled={unlockedStep < 3 || !job}
          >
            03 결과
            <small>weekly report</small>
          </button>
        </nav>

        <div className="main">
          {error ? <div className="error">{error}</div> : null}

          {step === 1 && (
            <section className="stage" key="step-1">
              <div className="stage-head">
                <h2>메일 백업 업로드</h2>
                <p>받은함 / 보낸함 각각 .zip 파일을 올려주세요. 업로드가 끝난 뒤에 다음 단계로 갈 수 있습니다.</p>
              </div>
              <div className="drop-grid">
                <UploadSlot
                  title="받은 메일함"
                  slot={inbox}
                  onPick={(file) => handlePick("inbox", file)}
                  onClear={() => clearSlot("inbox")}
                />
                <UploadSlot
                  title="보낸 메일함"
                  slot={sent}
                  onPick={(file) => handlePick("sent", file)}
                  onClear={() => clearSlot("sent")}
                />
              </div>
              <div className="actions">
                <button
                  type="button"
                  className="accent"
                  onClick={goNextFromUpload}
                  disabled={!canGoNextFromUpload}
                >
                  다음 단계
                </button>
              </div>
            </section>
          )}

          {step === 2 && (
            <section className="stage" key="step-2">
              <div className="stage-head">
                <h2>분석 조건</h2>
                <p>기간과 메일함 범위를 정한 뒤 분석을 시작합니다.</p>
              </div>
              <div className="row">
                <label className="field">
                  <span>시작일</span>
                  <input type="date" value={dateFrom} onChange={(e) => setDateFrom(e.target.value)} />
                </label>
                <label className="field">
                  <span>종료일</span>
                  <input type="date" value={dateTo} onChange={(e) => setDateTo(e.target.value)} />
                </label>
              </div>
              <label className="field">
                <span>분석 범위</span>
                <select value={mailboxScope} onChange={(e) => setMailboxScope(e.target.value)}>
                  <option value="both" disabled={!inboxReady || !sentReady}>
                    받은 + 보낸 (권장)
                  </option>
                  <option value="inbox" disabled={!inboxReady}>
                    받은 메일함만
                  </option>
                  <option value="sent" disabled={!sentReady}>
                    보낸 메일함만
                  </option>
                </select>
              </label>
              <div className="actions">
                <button type="button" onClick={() => setStep(1)}>
                  이전
                </button>
                <button
                  type="button"
                  className="accent"
                  disabled={!canAnalyze || busy}
                  onClick={handleStart}
                >
                  {busy ? "작업 생성 중…" : "분석 시작"}
                </button>
              </div>
            </section>
          )}

          {step === 3 && job && (
            <section className="stage" key="step-3">
              <div className="stage-head">
                <h2>분석 결과</h2>
                <div className="meta-row">
                  <span>{scopeLabel(job.mailbox_scope)}</span>
                  <span>
                    {job.date_from} ~ {job.date_to}
                  </span>
                  <span>
                    받은 {job.filtered_inbox_count ?? 0} / 보낸 {job.filtered_sent_count ?? 0}
                  </span>
                  <span>{STATUS_LABEL[job.status] || job.status}</span>
                </div>
              </div>

              {running ? (
                <div className="progress analysis-progress">
                  <div className="analysis-progress-head">
                    <strong>
                      <span className="dot" />
                      job #{job.id} 분석 진행 중
                    </strong>
                    <span>{Math.max(0, Math.min(100, job.progress_pct || 0))}%</span>
                  </div>
                  <div className="upload-bar analysis-bar" aria-hidden="true">
                    <i style={{ width: `${Math.max(2, Math.min(100, job.progress_pct || 0))}%` }} />
                  </div>
                  <p className="progress-label">{job.progress_label || STATUS_LABEL[job.status] || job.status}</p>
                  <p className="progress-detail">
                    {job.progress_detail ||
                      "대용량 메일 zip을 검사하는 중입니다. 완료될 때까지 이 화면에 머물러 주세요."}
                  </p>
                  <ul className="phase-list">
                    {ANALYSIS_STEPS.map((item) => {
                      const state = stepState(job.status, item.key);
                      return (
                        <li key={item.key} className={state}>
                          {item.label}
                        </li>
                      );
                    })}
                  </ul>
                  <p className="progress-counts">
                    검사 기준 메일 수 · 받은 {job.inbox_count ?? 0} / 보낸 {job.sent_count ?? 0}
                    {" · "}
                    기간 매칭 · 받은 {job.filtered_inbox_count ?? 0} / 보낸 {job.filtered_sent_count ?? 0}
                  </p>
                </div>
              ) : null}

              {report ? (
                <>
                  <div className="weekly-panel">
                    <div className="weekly-panel-head">
                      <h3>주간 업무보고</h3>
                      <button
                        type="button"
                        className={`icon-copy ${copied ? "copied" : ""}`}
                        onClick={copyReport}
                        disabled={!report.report_markdown}
                        aria-label={copied ? "복사됨" : "보고서 복사"}
                        title={copied ? "복사됨" : "복사"}
                      >
                        {copied ? <CheckIcon /> : <CopyIcon />}
                      </button>
                    </div>
                    <div className="weekly-table">
                      <WeeklySide title="금주" groups={data.this_week} stream={streamSummary} />
                      <WeeklySide title="차주" groups={data.next_week} stream={streamSummary} />
                    </div>
                  </div>
                  <div className="actions">
                    <button type="button" className="accent" onClick={goBackToConditions}>
                      조건으로 돌아가기
                    </button>
                    <button type="button" className="primary" onClick={goHome}>
                      새 분석
                    </button>
                  </div>
                </>
              ) : (
                !running && (
                  <div className="actions">
                    <button type="button" className="accent" onClick={goBackToConditions}>
                      조건으로 돌아가기
                    </button>
                    <button type="button" className="primary" onClick={goHome}>
                      새 분석
                    </button>
                  </div>
                )
              )}
            </section>
          )}
        </div>
      </div>
    </div>
  );
}
