import { useState } from 'react'
import { useParams, Link } from 'react-router-dom'
import { useRunDetail, useJobLogs, useCancelRun, useRetryJob } from '../api/queries/pipelines'
import { useRunSBOM, getRunSBOMDocumentURL, useGenerateSBOM } from '../api/queries/sbom'
import { Skeleton } from '../ui/Skeleton'
import { cn } from '../lib/utils'
import type { PipelineJob, PipelineStep, RunStatus } from '../types/api'

// ── Utilities ─────────────────────────────────────────────────────────────────

function timeAgo(iso: string | null): string {
  if (!iso) return '—'
  const diff = Date.now() - new Date(iso).getTime()
  const min = Math.floor(diff / 60_000)
  if (min < 1) return 'just now'
  if (min < 60) return `${min}m ago`
  const hr = Math.floor(min / 60)
  if (hr < 24) return `${hr}h ago`
  return `${Math.floor(hr / 24)}d ago`
}

function duration(start: string | null, end: string | null): string {
  if (!start) return '—'
  const ms = new Date(end ?? Date.now()).getTime() - new Date(start).getTime()
  const s = Math.floor(ms / 1000)
  if (s < 60) return `${s}s`
  const m = Math.floor(s / 60)
  return `${m}m ${s % 60}s`
}

// ── SBOM section ──────────────────────────────────────────────────────────────

function SBOMSection({ owner, repo, runId, runStatus, triggerSha }: {
  owner: string
  repo: string
  runId: number
  runStatus: RunStatus
  triggerSha: string
}) {
  const { data: sbom, isLoading } = useRunSBOM(owner, repo, runId)
  const generateSBOM = useGenerateSBOM(owner, repo)

  if (isLoading) {
    return (
      <section className="border border-[var(--c-border)] rounded-lg bg-[var(--c-surface)] p-4">
        <h2 className="text-xs font-semibold uppercase tracking-wider text-[var(--c-muted)] mb-3">
          SBOM
        </h2>
        <Skeleton className="h-5 w-64 rounded" />
      </section>
    )
  }

  if (sbom) {
    return (
      <section className="border border-[var(--c-border)] rounded-lg bg-[var(--c-surface)] p-4">
        <div className="flex items-center justify-between gap-4">
          <div>
            <h2 className="text-xs font-semibold uppercase tracking-wider text-[var(--c-muted)] mb-1">
              SBOM — CycloneDX
            </h2>
            <div className="flex items-center gap-3 text-xs text-[var(--c-muted)]">
              <span>{sbom.componentCount} components</span>
              <span className="font-mono">{sbom.sha.slice(0, 7)}</span>
              <span>{new Date(sbom.generatedAt).toLocaleString()}</span>
            </div>
          </div>
          <a
            href={getRunSBOMDocumentURL(owner, repo, runId)}
            download="bom.json"
            className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium border border-[var(--c-border)] rounded-lg text-[var(--c-text)] hover:bg-[var(--c-surface-muted)] transition-colors shrink-0"
          >
            <svg width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
            </svg>
            Download BOM
          </a>
        </div>
      </section>
    )
  }

  // No SBOM yet — show generate option for completed/failed runs
  if (runStatus === 'succeeded' || runStatus === 'failed') {
    return (
      <section className="border border-[var(--c-border)] rounded-lg bg-[var(--c-surface)] p-4">
        <div className="flex items-center justify-between gap-4">
          <div>
            <h2 className="text-xs font-semibold uppercase tracking-wider text-[var(--c-muted)] mb-1">
              SBOM
            </h2>
            <p className="text-xs text-[var(--c-muted)]">No SBOM generated for this run.</p>
          </div>
          <button
            onClick={() => generateSBOM.mutate({ ref: triggerSha, runId })}
            disabled={generateSBOM.isPending}
            className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium border border-[var(--c-border)] rounded-lg text-[var(--c-text)] hover:bg-[var(--c-surface-muted)] transition-colors disabled:opacity-50 shrink-0"
          >
            {generateSBOM.isPending ? 'Generating…' : 'Generate SBOM'}
          </button>
        </div>
        {generateSBOM.isError && (
          <p className="mt-2 text-xs text-[var(--c-danger)]">{(generateSBOM.error as Error).message}</p>
        )}
      </section>
    )
  }

  return null
}

function shortRef(ref: string): string {
  return ref.replace('refs/heads/', '').replace('refs/tags/', '')
}

function shortSHA(sha: string): string {
  return sha.slice(0, 7)
}

// ── Status helpers ─────────────────────────────────────────────────────────────

const STATUS_DOT: Record<RunStatus, string> = {
  queued:    'bg-[var(--c-subtle)]',
  running:   'bg-[var(--c-brand)] animate-pulse',
  succeeded: 'bg-[var(--c-success)]',
  failed:    'bg-[var(--c-danger)]',
  cancelled: 'bg-[var(--c-subtle)]',
}

const STATUS_BADGE: Record<RunStatus, string> = {
  queued:    'bg-[var(--c-surface-muted)] border-[var(--c-border)] text-[var(--c-muted)]',
  running:   'bg-[var(--c-brand-tint)] border-[var(--c-brand-focus)] text-[var(--c-brand)]',
  succeeded: 'bg-[#E3FCEF] border-[#79F2C0] text-[#006644]',
  failed:    'bg-[var(--c-danger-tint)] border-[#FF8F73] text-[var(--c-danger-dark)]',
  cancelled: 'bg-[var(--c-surface-muted)] border-[var(--c-border)] text-[var(--c-muted)]',
}

const STATUS_LABEL: Record<RunStatus, string> = {
  queued:    'Queued',
  running:   'Running',
  succeeded: 'Passed',
  failed:    'Failed',
  cancelled: 'Cancelled',
}

function StatusBadge({ status, size = 'md' }: { status: RunStatus; size?: 'sm' | 'md' }) {
  return (
    <span className={cn(
      'inline-flex items-center gap-1.5 font-medium border rounded-full',
      size === 'sm' ? 'text-[10px] px-1.5 py-px' : 'text-xs px-2.5 py-1',
      STATUS_BADGE[status],
    )}>
      <span className={cn('rounded-full shrink-0', size === 'sm' ? 'w-1.5 h-1.5' : 'w-2 h-2', STATUS_DOT[status])} />
      {STATUS_LABEL[status]}
    </span>
  )
}

// ── DAG visualization ─────────────────────────────────────────────────────────

type JobWithSteps = PipelineJob & { steps: PipelineStep[] }

interface DAGProps {
  jobs: JobWithSteps[]
  selectedJobId: number | null
  onSelectJob: (id: number) => void
}

function DAGView({ jobs, selectedJobId, onSelectJob }: DAGProps) {
  // Build dependency columns using topological sort
  const columns = topoColumns(jobs)

  if (jobs.length === 0) {
    return (
      <div className="flex items-center justify-center py-8 text-sm text-[var(--c-muted)]">
        No jobs in this run.
      </div>
    )
  }

  return (
    <div className="overflow-x-auto pb-2">
      <div className="flex items-start gap-0 min-w-max">
        {columns.map((col, colIdx) => (
          <div key={colIdx} className="flex items-center">
            {/* Column of jobs */}
            <div className="flex flex-col gap-2">
              {col.map(job => (
                <button
                  key={job.id}
                  onClick={() => onSelectJob(job.id)}
                  className={cn(
                    'flex flex-col gap-1.5 px-4 py-3 rounded-lg border text-left min-w-[140px] transition-all',
                    selectedJobId === job.id
                      ? 'ring-2 ring-[var(--c-brand)] border-[var(--c-brand-focus)]'
                      : 'hover:border-[var(--c-brand-focus)]',
                    STATUS_BADGE[job.status as RunStatus] || STATUS_BADGE.queued,
                  )}
                >
                  <div className="flex items-center gap-1.5">
                    <JobStatusIcon status={job.status as RunStatus} />
                    <span className="text-xs font-semibold truncate max-w-[100px]">{job.name}</span>
                  </div>
                  <span className="text-[10px] opacity-70">
                    {duration(job.startedAt, job.finishedAt)}
                  </span>
                  <span className="text-[10px] font-mono opacity-60 truncate">{job.image}</span>
                </button>
              ))}
            </div>

            {/* Connector (not after last column) */}
            {colIdx < columns.length - 1 && (
              <div className="flex items-center px-2 self-center">
                <div className="h-px w-4 bg-[var(--c-border)]" />
                <svg width="5" height="8" viewBox="0 0 5 8" fill="var(--c-border)">
                  <path d="M0 0l5 4-5 4V0z" />
                </svg>
              </div>
            )}
          </div>
        ))}
      </div>
    </div>
  )
}

function JobStatusIcon({ status }: { status: RunStatus }) {
  if (status === 'succeeded') {
    return <svg width="13" height="13" fill="none" stroke="var(--c-success)" strokeWidth="2.5" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="m4.5 12.75 6 6 9-13.5" /></svg>
  }
  if (status === 'failed') {
    return <svg width="13" height="13" fill="none" stroke="var(--c-danger)" strokeWidth="2.5" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M6 18 18 6M6 6l12 12" /></svg>
  }
  if (status === 'running') {
    return <svg width="13" height="13" fill="none" stroke="var(--c-brand)" strokeWidth="2" viewBox="0 0 24 24" className="animate-spin"><path strokeLinecap="round" d="M12 3a9 9 0 1 0 9 9" /></svg>
  }
  if (status === 'cancelled') {
    return <svg width="13" height="13" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M18.364 18.364A9 9 0 0 0 5.636 5.636m12.728 12.728A9 9 0 0 1 5.636 5.636m12.728 12.728L5.636 5.636" /></svg>
  }
  // queued
  return <svg width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.5" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" /></svg>
}

/** Arrange jobs into columns by dependency depth (0 = no deps). */
function topoColumns(jobs: JobWithSteps[]): JobWithSteps[][] {
  const nameToJob = new Map(jobs.map(j => [j.name, j]))
  const depth = new Map<string, number>()

  function getDepth(name: string, visited = new Set<string>()): number {
    if (depth.has(name)) return depth.get(name)!
    if (visited.has(name)) return 0
    visited.add(name)
    const job = nameToJob.get(name)
    if (!job) return 0
    let needs: string[] = []
    try { needs = JSON.parse(job.needs || '[]') ?? [] } catch { needs = [] }
    const d = needs.length === 0 ? 0 : 1 + Math.max(...needs.map(n => getDepth(n, new Set(visited))))
    depth.set(name, d)
    return d
  }

  jobs.forEach(j => getDepth(j.name))
  const maxDepth = Math.max(...Array.from(depth.values()), 0)
  const cols: JobWithSteps[][] = Array.from({ length: maxDepth + 1 }, () => [])
  jobs.forEach(j => cols[depth.get(j.name) ?? 0].push(j))
  return cols.filter(c => c.length > 0)
}

// ── Log viewer ─────────────────────────────────────────────────────────────────

function LogViewer({ owner, repo, runId, jobId }: {
  owner: string; repo: string; runId: number; jobId: number
}) {
  const { data, isLoading } = useJobLogs(owner, repo, runId, jobId)
  const [expandedSteps, setExpandedSteps] = useState<Set<number>>(new Set([0]))

  if (isLoading) {
    return (
      <div className="space-y-2 p-4">
        {[1, 2, 3].map(i => <Skeleton key={i} className="h-8 rounded" />)}
      </div>
    )
  }
  if (!data || data.length === 0) {
    return <p className="text-xs text-[var(--c-muted)] p-4">No steps recorded for this job.</p>
  }

  function toggle(seq: number) {
    setExpandedSteps(prev => {
      const next = new Set(prev)
      if (next.has(seq)) next.delete(seq)
      else next.add(seq)
      return next
    })
  }

  return (
    <div className="divide-y divide-[var(--c-border)]">
      {data.map(step => {
        const open = expandedSteps.has(step.seq)
        const logText = step.logs.map(l => l.content).join('')
        return (
          <div key={step.id}>
            <button
              onClick={() => toggle(step.seq)}
              className="w-full flex items-center gap-2 px-4 py-2.5 text-left hover:bg-[var(--c-surface-muted)] transition-colors"
            >
              {/* Chevron */}
              <svg
                width="12" height="12" fill="none" stroke="var(--c-muted)" strokeWidth="2" viewBox="0 0 24 24"
                className={cn('shrink-0 transition-transform', open ? 'rotate-90' : '')}
              >
                <path strokeLinecap="round" strokeLinejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
              </svg>
              <JobStatusIcon status={step.status as RunStatus} />
              <span className="flex-1 text-xs font-medium text-[var(--c-text)] truncate">
                {step.name || step.runCmd || step.usesAction || `Step ${step.seq + 1}`}
              </span>
              <span className="text-[10px] text-[var(--c-muted)] shrink-0">
                {duration(step.startedAt, step.finishedAt)}
              </span>
              {step.exitCode !== 0 && step.status === 'failed' && (
                <span className="text-[10px] font-mono text-[var(--c-danger)] shrink-0">
                  exit {step.exitCode}
                </span>
              )}
            </button>
            {open && (
              <div className="bg-[#0d1117] px-4 py-3 overflow-x-auto">
                {logText ? (
                  <pre className="text-[11px] leading-5 font-mono text-[#c9d1d9] whitespace-pre-wrap break-words">
                    {logText}
                  </pre>
                ) : (
                  <p className="text-[11px] text-[#8b949e] font-mono italic">No output captured.</p>
                )}
              </div>
            )}
          </div>
        )
      })}
    </div>
  )
}

// ── Main page ─────────────────────────────────────────────────────────────────

export default function PipelineRunPage() {
  const { owner = '', repo = '', runId = '' } = useParams()
  const runIdNum = parseInt(runId, 10)

  const { data: run, isLoading, error } = useRunDetail(owner, repo, runIdNum)
  const cancelRun = useCancelRun(owner, repo)
  const retryJob = useRetryJob(owner, repo, runIdNum)

  const [selectedJobId, setSelectedJobId] = useState<number | null>(null)

  // Auto-select first job once data loads
  const jobs = run?.jobs ?? []
  const effectiveJobId = selectedJobId ?? jobs[0]?.id ?? null
  const selectedJob = jobs.find(j => j.id === effectiveJobId) ?? null

  if (error) {
    return (
      <div className="max-w-4xl mx-auto px-4 py-12 text-center">
        <p className="text-sm text-[var(--c-muted)]">Run not found.</p>
        <Link to={`/repos/${owner}/${repo}`} className="mt-4 inline-block text-xs text-[var(--c-brand)] hover:underline">
          ← Back to repository
        </Link>
      </div>
    )
  }

  return (
    <div className="max-w-6xl mx-auto px-4 md:px-6 py-5 space-y-5">

      {/* Breadcrumb */}
      <nav className="flex items-center gap-1.5 text-xs text-[var(--c-muted)]">
        <Link to={`/repos/${owner}/${repo}`} className="hover:text-[var(--c-brand)] transition-colors">
          {owner}/{repo}
        </Link>
        <span>/</span>
        <Link to={`/repos/${owner}/${repo}`} className="hover:text-[var(--c-brand)] transition-colors">
          Runs
        </Link>
        <span>/</span>
        <span className="text-[var(--c-text)]">#{runId}</span>
      </nav>

      {/* Run header */}
      {isLoading ? (
        <div className="border border-[var(--c-border)] rounded-lg bg-[var(--c-surface)] p-4 space-y-3">
          <Skeleton className="h-5 w-48 rounded" />
          <Skeleton className="h-3.5 w-80 rounded" />
        </div>
      ) : run ? (
        <div className="border border-[var(--c-border)] rounded-lg bg-[var(--c-surface)] p-4">
          <div className="flex items-start justify-between gap-4 flex-wrap">
            <div className="space-y-1.5">
              <div className="flex items-center gap-2.5 flex-wrap">
                <h1 className="text-base font-semibold text-[var(--c-text)]">
                  Run #{run.id}
                </h1>
                <StatusBadge status={run.status as RunStatus} />
              </div>
              <div className="flex items-center gap-3 text-xs text-[var(--c-muted)] flex-wrap">
                <span className="flex items-center gap-1">
                  <svg width="11" height="11" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
                    <path strokeLinecap="round" strokeLinejoin="round" d="M9 17.25v1.007a3 3 0 0 1-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0 1 15 18.257V17.25m6-12V15a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 15V5.25m18 0A2.25 2.25 0 0 0 18.75 3H5.25A2.25 2.25 0 0 0 3 5.25m18 0H3" />
                  </svg>
                  <span className="font-mono">{shortRef(run.triggerRef)}</span>
                </span>
                <span className="font-mono text-[var(--c-muted)]">{shortSHA(run.triggerSha)}</span>
                <span>triggered by {run.triggeredBy}</span>
                <span>{timeAgo(run.createdAt)}</span>
                {run.startedAt && (
                  <span>duration: {duration(run.startedAt, run.finishedAt)}</span>
                )}
              </div>
            </div>

            {/* Actions */}
            <div className="flex items-center gap-2">
              {(run.status === 'running' || run.status === 'queued') && (
                <button
                  onClick={() => cancelRun.mutate(run.id)}
                  disabled={cancelRun.isPending}
                  className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium border border-[var(--c-border)] rounded-lg text-[var(--c-text)] hover:border-[var(--c-danger)] hover:text-[var(--c-danger)] transition-colors disabled:opacity-50"
                >
                  <svg width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
                    <path strokeLinecap="round" strokeLinejoin="round" d="M5.25 7.5A2.25 2.25 0 0 1 7.5 5.25h9a2.25 2.25 0 0 1 2.25 2.25v9a2.25 2.25 0 0 1-2.25 2.25h-9a2.25 2.25 0 0 1-2.25-2.25v-9Z" />
                  </svg>
                  Cancel
                </button>
              )}
            </div>
          </div>
        </div>
      ) : null}

      {/* SBOM section */}
      {!isLoading && run && (
        <SBOMSection owner={owner} repo={repo} runId={runIdNum} runStatus={run.status as RunStatus} triggerSha={run.triggerSha} />
      )}

      {/* DAG + log viewer */}
      <div className="grid grid-cols-1 gap-4">

        {/* DAG section */}
        <section>
          <h2 className="text-xs font-semibold uppercase tracking-wider text-[var(--c-muted)] mb-2.5">
            Jobs
          </h2>
          <div className="border border-[var(--c-border)] rounded-lg bg-[var(--c-surface)] p-4">
            {isLoading ? (
              <div className="flex gap-3">
                {[1, 2, 3].map(i => <Skeleton key={i} className="h-20 w-36 rounded-lg" />)}
              </div>
            ) : (
              <DAGView
                jobs={jobs}
                selectedJobId={effectiveJobId}
                onSelectJob={id => setSelectedJobId(id)}
              />
            )}
          </div>
        </section>

        {/* Log viewer for selected job */}
        {effectiveJobId !== null && (
          <section>
            <div className="flex items-center justify-between mb-2.5">
              <h2 className="text-xs font-semibold uppercase tracking-wider text-[var(--c-muted)]">
                {selectedJob ? `Logs — ${selectedJob.name}` : 'Logs'}
              </h2>
              <div className="flex items-center gap-2">
                {selectedJob && (
                  <StatusBadge status={selectedJob.status as RunStatus} size="sm" />
                )}
                {selectedJob && (selectedJob.status === 'failed' || selectedJob.status === 'cancelled') && (
                  <button
                    onClick={() => retryJob.mutate(effectiveJobId)}
                    disabled={retryJob.isPending}
                    className="flex items-center gap-1 px-2.5 py-1 text-[10px] font-medium border border-[var(--c-border)] rounded-lg text-[var(--c-text)] hover:border-[var(--c-brand-focus)] hover:text-[var(--c-brand)] transition-colors disabled:opacity-50"
                  >
                    <svg width="10" height="10" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
                      <path strokeLinecap="round" strokeLinejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
                    </svg>
                    Retry
                  </button>
                )}
              </div>
            </div>
            <div className="border border-[var(--c-border)] rounded-lg bg-[var(--c-surface)] overflow-hidden">
              <LogViewer owner={owner} repo={repo} runId={runIdNum} jobId={effectiveJobId} />
            </div>
          </section>
        )}
      </div>
    </div>
  )
}
