import { useEffect, useRef, useState, type ChangeEvent, type FormEvent } from 'react'
import { cancelAccount, createAccount, deleteAccount, getTask, importCSV, probeAccountFolders, probeFolders, runTask, setAccountFolderMapping, setTaskSchedule, testAccounts, type TaskDetail as TaskDetailData } from '../api'
import { connectTaskWS, type TaskEvent } from '../ws'
import { StatusBadge } from '../components/StatusBadge'
import { useConfirm } from '../components/ConfirmProvider'
import { FolderMappingModal } from '../components/FolderMappingModal'
import { RunLogModal } from '../components/RunLogModal'
import { AccountErrorsModal } from '../components/AccountErrorsModal'

const emptyAccount = { src_login: '', src_pass: '', dst_login: '', dst_pass: '' }

// Live per-account progress derived from throttled `progress` WS events.
type LiveProgress = {
  copied: number
  skipped: number
  total: number // account-wide message total from the planning pass (0 if unknown)
  folder?: string
  startTs: number
  startCount: number
  speed: number // messages/sec, averaged since the account's run started
  scanFolder?: string
  scanned?: number
  scanTotal?: number
}

function fmtDuration(sec: number): string {
  if (!isFinite(sec) || sec < 0) return '—'
  const m = Math.floor(sec / 60)
  const s = Math.floor(sec % 60)
  return `${m}:${String(s).padStart(2, '0')}`
}

// Human-readable one-line description of a task event for the log panel.
function describeEvent(ev: TaskEvent): string {
  const d = (ev.data ?? {}) as Record<string, unknown>
  const at = d.host ? `${d.login ?? ''}@${d.host}:${d.port}` : ''
  switch (ev.type) {
    case 'account_test': {
      const where = d.side === 'src' ? 'SOURCE' : 'DEST'
      const base = `${where} test ${String(d.status).toUpperCase()} — ${at}`
      return d.error ? `${base} — ${d.error}` : base
    }
    case 'account_started':
      return `START #${d.account_id}: ${d.src_login}@${d.src_host}:${d.src_port} → ${d.dst_login}@${d.dst_host}:${d.dst_port}`
    case 'plan':
      return `PLAN #${d.account_id} (${d.src_login}): ${d.folders} folders, ${d.total} messages total`
    case 'account_done':
      return `DONE #${d.account_id} (${d.src_login} → ${d.dst_login}): copied ${d.copied}, skipped ${d.skipped}, errors ${d.errors}`
    case 'progress': {
      const pct = d.folder_total ? Math.floor((Number(d.folder_done) / Number(d.folder_total)) * 100) : 0
      const loc = d.folder ? `"${d.folder}" ${d.folder_done}/${d.folder_total} (${pct}%) · ` : ''
      return `progress #${d.account_id}: ${loc}copied ${d.copied}, skipped ${d.skipped}`
    }
    case 'folder': {
      const route = d.dst_folder && d.dst_folder !== d.folder ? ` → "${d.dst_folder}"` : ''
      return `folder "${d.folder}"${route}: ${d.messages ?? 0} messages — fetching (#${d.account_id})`
    }
    case 'cancelled':
      return `CANCELLED #${d.account_id} (${d.src_login}): copied ${d.copied ?? 0}, skipped ${d.skipped ?? 0}`
    case 'error': {
      const where = d.folder ? ` folder "${d.folder}"` : d.side ? ` (${d.side} ${at})` : ''
      return `ERROR #${d.account_id}${where}: ${d.error}`
    }
    case 'run_started':
      return `RUN started (run #${d.run_id})`
    case 'run_done':
      return `RUN finished: copied ${d.copied}, skipped ${d.skipped}, errors ${d.errors}`
    default:
      return JSON.stringify(ev.data)
  }
}

export function TaskDetail({ id }: { id: number }) {
  const [data, setData] = useState<TaskDetailData | null>(null)
  const [notFound, setNotFound] = useState(false)
  const [log, setLog] = useState<{ type: string; text: string }[]>([])
  const [form, setForm] = useState(emptyAccount)
  const [busy, setBusy] = useState<'test' | 'run' | 'add' | 'import' | 'delete' | 'probe' | null>(null)
  const [mapState, setMapState] = useState<{ src: string[]; dst: string[]; creds: typeof emptyAccount } | null>(null)
  const [editMap, setEditMap] = useState<{
    accId: number
    src: string[]
    dst: string[]
    mapping: Record<string, string>
    excluded: string[]
  } | null>(null)
  const confirm = useConfirm()
  const [error, setError] = useState<string | null>(null)
  const [live, setLive] = useState<Record<number, LiveProgress>>({})
  const [showRuns, setShowRuns] = useState(false)
  const [errorsFor, setErrorsFor] = useState<{ id: number; src_login: string } | null>(null)
  const fileInputRef = useRef<HTMLInputElement>(null)

  function reload() {
    getTask(id)
      .then((d) => {
        setData(d)
        setNotFound(false)
      })
      .catch(() => setNotFound(true))
  }

  useEffect(reload, [id])

  useEffect(
    () =>
      connectTaskWS(id, (ev: TaskEvent) => {
        // `scan` is high-frequency and shown in the progress cell, not the log.
        if (ev.type !== 'scan') {
          setLog((l) => [{ type: ev.type, text: describeEvent(ev) }, ...l].slice(0, 300))
        }
        const d = (ev.data ?? {}) as Record<string, number | string | undefined>
        const accId = typeof d.account_id === 'number' ? d.account_id : undefined

        if (ev.type === 'scan' && accId != null) {
          setLive((prev) => {
            const cur = prev[accId]
            const base: LiveProgress = cur ?? { copied: 0, skipped: 0, total: 0, startTs: Date.now(), startCount: 0, speed: 0 }
            return { ...prev, [accId]: { ...base, scanFolder: d.folder as string | undefined, scanned: Number(d.scanned ?? 0), scanTotal: Number(d.folder_total ?? 0) } }
          })
        } else if (ev.type === 'plan' && accId != null) {
          const total = Number(d.total ?? 0)
          const now = Date.now()
          setLive((prev) => ({
            ...prev,
            [accId]: {
              copied: prev[accId]?.copied ?? 0,
              skipped: prev[accId]?.skipped ?? 0,
              total,
              folder: prev[accId]?.folder,
              startTs: prev[accId]?.startTs ?? now,
              startCount: prev[accId]?.startCount ?? 0,
              speed: prev[accId]?.speed ?? 0,
            },
          }))
        } else if (ev.type === 'progress' && accId != null) {
          const now = Date.now()
          const copied = Number(d.copied ?? 0)
          const skipped = Number(d.skipped ?? 0)
          const processed = copied + skipped
          setLive((prev) => {
            const cur = prev[accId]
            const startTs = cur?.startTs ?? now
            const startCount = cur?.startCount ?? processed
            const dt = (now - startTs) / 1000
            const speed = dt > 0.5 ? (processed - startCount) / dt : (cur?.speed ?? 0)
            return {
              ...prev,
              [accId]: {
                copied,
                skipped,
                total: Number(d.account_total ?? cur?.total ?? 0),
                folder: d.folder as string | undefined,
                startTs,
                startCount,
                speed,
              },
            }
          })
        } else if (accId != null && (ev.type === 'account_started' || ev.type === 'account_done' || ev.type === 'cancelled' || (ev.type === 'error' && d.folder == null))) {
          // terminal/reset for this account — drop live overlay, fall back to DB
          setLive((prev) => {
            if (!(accId in prev)) return prev
            const next = { ...prev }
            delete next[accId]
            return next
          })
        }

        // Structural events refresh the persisted view; `progress` is covered by live state.
        if (['account_started', 'account_test', 'account_done', 'run_started', 'run_done', 'error', 'folder', 'cancelled', 'plan', 'task_broken'].includes(ev.type)) {
          reload()
        }
      }),
    [id],
  )

  async function submitAccount(e: FormEvent) {
    e.preventDefault()
    setBusy('probe')
    setError(null)
    try {
      const creds = { ...form }
      const res = await probeFolders(id, creds)
      if (!res.src.ok || !res.dst.ok) {
        const parts: string[] = []
        if (!res.src.ok) parts.push(`source: ${res.src.error ?? 'login failed'}`)
        if (!res.dst.ok) parts.push(`destination: ${res.dst.error ?? 'login failed'}`)
        setError(`Connection test failed — ${parts.join('; ')}`)
        return
      }
      setMapState({ src: res.src.folders ?? [], dst: res.dst.folders ?? [], creds })
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to test connections')
    } finally {
      setBusy(null)
    }
  }

  async function confirmMapping(mapping: Record<string, string>, excluded: string[]) {
    if (!mapState) return
    setBusy('add')
    setError(null)
    try {
      const { id: accId } = await createAccount(id, mapState.creds)
      await setAccountFolderMapping(id, accId, mapping, excluded)
      setForm(emptyAccount)
      setMapState(null)
      reload()
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to add account')
    } finally {
      setBusy(null)
    }
  }

  async function onEditFolders(a: { id: number; src_login: string; folder_mapping?: Record<string, string>; excluded_folders?: string[] }) {
    setBusy('probe')
    setError(null)
    try {
      const res = await probeAccountFolders(id, a.id)
      if (!res.src.ok || !res.dst.ok) {
        const parts: string[] = []
        if (!res.src.ok) parts.push(`source: ${res.src.error ?? 'login failed'}`)
        if (!res.dst.ok) parts.push(`destination: ${res.dst.error ?? 'login failed'}`)
        setError(`Folder probe failed — ${parts.join('; ')}`)
        return
      }
      setEditMap({
        accId: a.id,
        src: res.src.folders ?? [],
        dst: res.dst.folders ?? [],
        mapping: a.folder_mapping ?? {},
        excluded: a.excluded_folders ?? [],
      })
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to probe folders')
    } finally {
      setBusy(null)
    }
  }

  async function saveEditMapping(mapping: Record<string, string>, excluded: string[]) {
    if (!editMap) return
    setBusy('add')
    setError(null)
    try {
      await setAccountFolderMapping(id, editMap.accId, mapping, excluded)
      setEditMap(null)
      reload()
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to save mapping')
    } finally {
      setBusy(null)
    }
  }

  function downloadExampleCSV() {
    const sample = [
      'alice@source.example,SrcPass1,alice@dest.example,DstPass1',
      'bob@source.example,SrcPass2,bob@dest.example,DstPass2',
      'carol@source.example,SrcPass3,carol@dest.example,DstPass3',
    ].join('\n') + '\n'
    const url = URL.createObjectURL(new Blob([sample], { type: 'text/csv' }))
    const a = document.createElement('a')
    a.href = url
    a.download = 'imap-copier-accounts-example.csv'
    a.click()
    URL.revokeObjectURL(url)
  }

  async function onFileChosen(e: ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0]
    if (!file) return
    setBusy('import')
    setError(null)
    try {
      await importCSV(id, file)
      reload()
    } catch (err) {
      setError(err instanceof Error ? err.message : 'CSV import failed')
    } finally {
      setBusy(null)
      if (fileInputRef.current) fileInputRef.current.value = ''
    }
  }

  async function onDeleteAccount(accId: number, login: string) {
    const ok = await confirm({
      title: 'Remove account',
      message: `Remove account "${login}" from this task?`,
      confirmLabel: 'Remove',
      danger: true,
    })
    if (!ok) return
    setBusy('delete')
    setError(null)
    try {
      await deleteAccount(id, accId)
      reload()
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to remove account')
    } finally {
      setBusy(null)
    }
  }

  async function onCancelAccount(accId: number) {
    setError(null)
    try {
      await cancelAccount(id, accId)
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to cancel account')
    }
  }

  async function onTest() {
    setBusy('test')
    setError(null)
    try {
      await testAccounts(id)
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to start connection tests')
    } finally {
      setBusy(null)
    }
  }

  async function onRun() {
    setBusy('run')
    setError(null)
    try {
      await runTask(id)
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to start run')
    } finally {
      setBusy(null)
    }
  }

  async function onSchedule(intervalSeconds: number) {
    setError(null)
    try {
      await setTaskSchedule(id, intervalSeconds)
      reload()
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to set schedule')
    }
  }

  if (notFound) {
    return (
      <div className="panel">
        <p>Task #{id} not found.</p>
        <a className="crumb" href="#/">
          ← back to tasks
        </a>
      </div>
    )
  }

  if (!data) {
    return <div className="muted-note">loading task #{id}…</div>
  }

  const { task, accounts } = data
  const allTested = accounts.length > 0 && accounts.every((a) => a.test_src_status === 'ok' && a.test_dst_status === 'ok')
  // Prefer live (WS) copied/skipped over the DB values, which only advance per
  // folder — so the summary moves in real time during a large folder.
  const totals = accounts.reduce(
    (acc, a) => ({
      copied: acc.copied + (live[a.id]?.copied ?? a.copied),
      skipped: acc.skipped + (live[a.id]?.skipped ?? a.skipped),
      errors: acc.errors + a.errors,
    }),
    { copied: 0, skipped: 0, errors: 0 },
  )

  return (
    <>
      <div className="page-head">
        <div>
          <a className="crumb" href="#/">
            ← all tasks
          </a>
          <h1 className="page-title" style={{ marginTop: 6 }}>
            {task.name} <span className="idx">/// task #{task.id}</span>
          </h1>
        </div>
        <StatusBadge status={task.status} />
        {task.broken && <span className="badge badge-fail" style={{ marginLeft: 8 }}><span className="dot" />broken</span>}
      </div>

      <div className="panel">
        <span className="panel-label">Run control</span>
        <div className="stat-row" aria-live="polite">
          <div className="stat ok">
            <span className="val mono-num">{totals.copied}</span>
            <span className="lbl">copied</span>
          </div>
          <div className="stat info">
            <span className="val mono-num">{totals.skipped}</span>
            <span className="lbl">skipped</span>
          </div>
          <div className="stat fail">
            <span className="val mono-num">{totals.errors}</span>
            <span className="lbl">errors</span>
          </div>
          <div className="stat">
            <span className="val mono-num">{accounts.length}</span>
            <span className="lbl">accounts</span>
          </div>
        </div>
        {error && <div className="error-banner" role="alert">{error}</div>}
        <div className="btn-row" style={{ marginTop: 16 }}>
          <button className="btn" onClick={onTest} disabled={busy !== null || accounts.length === 0}>
            {busy === 'test' ? 'Testing…' : 'Test connections'}
          </button>
          <button className="btn btn-primary" onClick={onRun} disabled={busy !== null || !allTested || task.status === 'running'}>
            {busy === 'run' ? 'Starting…' : 'Run migration'}
          </button>
          {!allTested && accounts.length > 0 && <span className="hint">run unlocks once every account tests OK on both sides</span>}
        </div>
        <div className="sched-row">
          <label htmlFor="sched">Schedule</label>
          <select
            id="sched"
            value={task.schedule_interval_seconds ?? 0}
            onChange={(e) => onSchedule(Number(e.target.value))}
          >
            <option value={0}>Off</option>
            <option value={3600}>Every 1h</option>
            <option value={10800}>Every 3h</option>
            <option value={21600}>Every 6h</option>
            <option value={43200}>Every 12h</option>
            <option value={86400}>Every 24h</option>
          </select>
          {task.next_run_at && (
            <span className="sched-next">Next run: {new Date(task.next_run_at).toLocaleString()}</span>
          )}
          {task.broken && <span className="badge badge-fail"><span className="dot" />broken</span>}
          <button type="button" className="link-btn" onClick={() => setShowRuns(true)}>
            runs
          </button>
        </div>
      </div>

      <div className="panel-grid">
        <div className="panel">
          <span className="panel-label">Add account</span>
          <form onSubmit={submitAccount}>
            <div className="field-row">
              <div className="field">
                <label htmlFor="src_login">Source login</label>
                <input
                  id="src_login"
                  value={form.src_login}
                  onChange={(e) => setForm({ ...form, src_login: e.target.value })}
                  required
                />
              </div>
              <div className="field">
                <label htmlFor="src_pass">Source password</label>
                <input
                  id="src_pass"
                  type="password"
                  value={form.src_pass}
                  onChange={(e) => setForm({ ...form, src_pass: e.target.value })}
                  required
                />
              </div>
            </div>
            <div className="field-row">
              <div className="field">
                <label htmlFor="dst_login">Destination login</label>
                <input
                  id="dst_login"
                  value={form.dst_login}
                  onChange={(e) => setForm({ ...form, dst_login: e.target.value })}
                  required
                />
              </div>
              <div className="field">
                <label htmlFor="dst_pass">Destination password</label>
                <input
                  id="dst_pass"
                  type="password"
                  value={form.dst_pass}
                  onChange={(e) => setForm({ ...form, dst_pass: e.target.value })}
                  required
                />
              </div>
            </div>
            <div className="btn-row">
              <button className="btn btn-primary" disabled={busy !== null}>
                {busy === 'probe' ? 'Testing…' : busy === 'add' ? 'Adding…' : 'Add account'}
              </button>
            </div>
          </form>

          <div className="divider-label">or bulk import</div>
          <div className="upload-row">
            <label className={`btn file-btn${busy !== null ? ' is-disabled' : ''}`}>
              {busy === 'import' ? 'Importing…' : 'Upload CSV'}
              <input ref={fileInputRef} type="file" accept=".csv,text/csv" onChange={onFileChosen} disabled={busy !== null} />
            </label>
            <button type="button" className="link-btn" onClick={downloadExampleCSV}>
              download example.csv
            </button>
          </div>
        </div>

        <div className="panel">
          <span className="panel-label">Event log</span>
          {log.length > 0 && (
            <button type="button" className="link-btn log-clear" onClick={() => setLog([])}>
              clear log
            </button>
          )}
          <div className="log-pane" role="log" aria-live="polite" aria-relevant="additions" aria-label="Event log">
            {log.length === 0 ? (
              <div className="log-empty">awaiting events over websocket…</div>
            ) : (
              log.map((l, i) => (
                <div className="log-line" key={i}>
                  <span className="tag">{l.type}</span>
                  <span className="payload">{l.text}</span>
                </div>
              ))
            )}
          </div>
        </div>
      </div>

      <div className="panel">
        <span className="panel-label">Accounts ({accounts.length})</span>
        <div className="tbl-wrap">
          <table className="tbl">
            <thead>
              <tr>
                <th>Account</th>
                <th>Src test</th>
                <th>Dst test</th>
                <th>Status</th>
                <th>Progress</th>
                <th>Copied</th>
                <th>Skipped</th>
                <th>Errors</th>
                <th></th>
              </tr>
            </thead>
            <tbody>
              {accounts.length === 0 ? (
                <tr className="empty-row">
                  <td colSpan={9}>no accounts yet — add one or import a CSV above</td>
                </tr>
              ) : (
                accounts.map((a) => (
                  <tr key={a.id}>
                    <td>
                      <div className="acct-ident">
                        <span>{a.src_login}</span>
                        {a.dst_login !== a.src_login && (
                          <span className="acct-dst">→ {a.dst_login}</span>
                        )}
                      </div>
                      {a.last_error && (
                        <div className="acct-error" title={a.last_error}>
                          {a.last_error}
                        </div>
                      )}
                    </td>
                    <td>
                      <StatusBadge status={a.test_src_status} />
                    </td>
                    <td>
                      <StatusBadge status={a.test_dst_status} />
                    </td>
                    <td>
                      <StatusBadge status={a.status} />
                    </td>
                    <td className={`progress-cell${a.status === 'running' ? ' progress-cell--live' : ''}`}>
                      {(() => {
                        const lv = live[a.id]
                        if (!lv || !lv.total) return <span className="muted-note">—</span>
                        const done = lv.copied + lv.skipped
                        const pct = Math.min(100, Math.floor((done / lv.total) * 100))
                        const eta = lv.speed > 0 ? (lv.total - done) / lv.speed : Infinity
                        const scanning = lv.scanned != null && lv.scanTotal != null && lv.scanned < lv.scanTotal
                        return (
                          <div className="acct-progress">
                            <div
                              className="pbar"
                              role="progressbar"
                              aria-valuenow={pct}
                              aria-valuemin={0}
                              aria-valuemax={100}
                              aria-label={`Copy progress${lv.folder ? `: ${lv.folder}` : ''}`}
                            >
                              <span className="pbar-fill" style={{ transform: `scaleX(${pct / 100})` }} />
                            </div>
                            <span className="pmeta mono-num">
                              {done}/{lv.total} ({pct}%) · {lv.speed >= 1 ? Math.round(lv.speed) : lv.speed.toFixed(1)}/s · ETA {fmtDuration(eta)}
                              {lv.folder ? ` · ${lv.folder}` : ''}
                            </span>
                            {scanning && (
                              <span className="pmeta pscan mono-num">
                                scanning {lv.scanFolder}: {lv.scanned}/{lv.scanTotal}
                              </span>
                            )}
                          </div>
                        )
                      })()}
                    </td>
                    <td className="num-cell">{live[a.id]?.copied ?? a.copied}</td>
                    <td className="num-cell">{live[a.id]?.skipped ?? a.skipped}</td>
                    <td className="num-cell">
                      {a.errors > 0 ? (
                        <button
                          type="button"
                          className="link-btn danger"
                          onClick={() => setErrorsFor({ id: a.id, src_login: a.src_login })}
                        >
                          {a.errors}
                        </button>
                      ) : (
                        a.errors
                      )}
                    </td>
                    <td className="num-cell">
                      <div className="row-actions">
                        {a.status !== 'running' && data?.task.status !== 'running' && (
                          <button
                            type="button"
                            className="link-btn"
                            onClick={() => onEditFolders(a)}
                            disabled={busy !== null}
                          >
                            folders
                          </button>
                        )}
                        {a.status === 'running' ? (
                          <button type="button" className="link-btn danger" onClick={() => onCancelAccount(a.id)}>
                            cancel
                          </button>
                        ) : (
                          <button
                            type="button"
                            className="link-btn danger"
                            onClick={() => onDeleteAccount(a.id, a.src_login)}
                            disabled={busy !== null || data?.task.status === 'running'}
                          >
                            remove
                          </button>
                        )}
                      </div>
                    </td>
                  </tr>
                ))
              )}
            </tbody>
          </table>
        </div>
      </div>

      {mapState && (
        <FolderMappingModal
          key={`${mapState.creds.src_login}|${mapState.creds.dst_login}`}
          open
          srcFolders={mapState.src}
          dstFolders={mapState.dst}
          initialMapping={task.folder_mapping ?? {}}
          initialExcluded={[]}
          onCancel={() => setMapState(null)}
          onConfirm={confirmMapping}
        />
      )}
      {editMap && (
        <FolderMappingModal
          key={`edit-${editMap.accId}`}
          open
          srcFolders={editMap.src}
          dstFolders={editMap.dst}
          initialMapping={editMap.mapping}
          initialExcluded={editMap.excluded}
          onCancel={() => setEditMap(null)}
          onConfirm={saveEditMapping}
        />
      )}
      <RunLogModal taskId={id} open={showRuns} onClose={() => setShowRuns(false)} />
      <AccountErrorsModal taskId={id} account={errorsFor} onClose={() => setErrorsFor(null)} />
    </>
  )
}
