/**
 * Unified CI/CD Pipeline Timeline — /:owner/:repo/pipeline
 *
 * Single scrollable view combining gate runs and deployments for a repo,
 * ordered chronologically. Operators can see the full story of a push —
 * from gate check through to production deploy — without clicking between
 * separate pages.
 *
 * Events shown:
 *   - Gate runs (status: passed/failed/running/skipped/repaired)
 *   - Cloud deployments (status: success/failed/blocked/running/pending)
 *
 * Refreshes every 15s while any event is in-flight.
 */

import { Hono } from "hono";
import { and, desc, eq, gte, or } from "drizzle-orm";
import { db } from "../db";
import { deployments, gateRuns, pullRequests, repositories, users } from "../db/schema";
import { softAuth } from "../middleware/auth";
import type { AuthEnv } from "../middleware/auth";
import { Layout } from "../views/layout";
import { RepoHeader, RepoNav } from "../views/components";

const app = new Hono<AuthEnv>();
app.use("/:owner/:repo/pipeline", softAuth);

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function relTime(d: Date): string {
  const sec = Math.floor((Date.now() - d.getTime()) / 1000);
  if (sec < 60) return `${sec}s ago`;
  if (sec < 3600) return `${Math.floor(sec / 60)}m ago`;
  if (sec < 86400) return `${Math.floor(sec / 3600)}h ago`;
  return `${Math.floor(sec / 86400)}d ago`;
}

function durationStr(startMs: number, endMs?: number | null): string {
  const ms = (endMs ?? Date.now()) - startMs;
  if (ms < 1000) return `${ms}ms`;
  if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
  return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`;
}

type GateEvent = {
  kind: "gate";
  id: string;
  createdAt: Date;
  completedAt: Date | null;
  status: string;
  gateName: string;
  commitSha: string;
  ref: string;
  prNumber: number | null;
  repairAttempted: boolean | null;
  repairSucceeded: boolean | null;
  durationMs: number | null;
};

type DeployEvent = {
  kind: "deploy";
  id: string;
  createdAt: Date;
  completedAt: Date | null;
  status: string;
  environment: string;
  ref: string;
  commitSha: string;
  blockedReason: string | null;
};

type TimelineEvent = GateEvent | DeployEvent;

// ---------------------------------------------------------------------------
// GET /:owner/:repo/pipeline
// ---------------------------------------------------------------------------

app.get("/:owner/:repo/pipeline", async (c) => {
  const { owner, repo } = c.req.param();

  const [repoRow] = await db
    .select({ repo: repositories, owner: users })
    .from(repositories)
    .innerJoin(users, eq(repositories.ownerId, users.id))
    .where(and(eq(users.username, owner), eq(repositories.name, repo)));

  if (!repoRow) return c.notFound();

  const authUser = c.get("user");
  const isOwner = authUser?.id === repoRow.owner.id;
  if (repoRow.repo.isPrivate && !isOwner) return c.notFound();

  const since = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); // last 7 days

  const [gateRows, deployRows] = await Promise.all([
    db
      .select({
        id: gateRuns.id,
        createdAt: gateRuns.createdAt,
        completedAt: gateRuns.completedAt,
        status: gateRuns.status,
        gateName: gateRuns.gateName,
        commitSha: gateRuns.commitSha,
        ref: gateRuns.ref,
        pullRequestId: gateRuns.pullRequestId,
        repairAttempted: gateRuns.repairAttempted,
        repairSucceeded: gateRuns.repairSucceeded,
        durationMs: gateRuns.durationMs,
      })
      .from(gateRuns)
      .where(and(eq(gateRuns.repositoryId, repoRow.repo.id), gte(gateRuns.createdAt, since)))
      .orderBy(desc(gateRuns.createdAt))
      .limit(100),
    db
      .select({
        id: deployments.id,
        createdAt: deployments.createdAt,
        completedAt: deployments.completedAt,
        status: deployments.status,
        environment: deployments.environment,
        ref: deployments.ref,
        commitSha: deployments.commitSha,
        blockedReason: deployments.blockedReason,
      })
      .from(deployments)
      .where(and(eq(deployments.repositoryId, repoRow.repo.id), gte(deployments.createdAt, since)))
      .orderBy(desc(deployments.createdAt))
      .limit(50),
  ]);

  // Resolve PR numbers for gate rows that have a pullRequestId
  const prIdSet = new Set(gateRows.map((g) => g.pullRequestId).filter(Boolean) as string[]);
  const prNumberMap = new Map<string, number>();
  if (prIdSet.size > 0) {
    const prRows = await db
      .select({ id: pullRequests.id, number: pullRequests.number })
      .from(pullRequests)
      .where(
        or(...Array.from(prIdSet).map((id) => eq(pullRequests.id, id)))
      );
    prRows.forEach((p) => prNumberMap.set(p.id, p.number));
  }

  const events: TimelineEvent[] = [
    ...gateRows.map((g): GateEvent => ({
      kind: "gate",
      id: g.id,
      createdAt: g.createdAt,
      completedAt: g.completedAt,
      status: g.status,
      gateName: g.gateName,
      commitSha: g.commitSha,
      ref: g.ref,
      prNumber: g.pullRequestId ? (prNumberMap.get(g.pullRequestId) ?? null) : null,
      repairAttempted: g.repairAttempted,
      repairSucceeded: g.repairSucceeded,
      durationMs: g.durationMs,
    })),
    ...deployRows.map((d): DeployEvent => ({
      kind: "deploy",
      id: d.id,
      createdAt: d.createdAt,
      completedAt: d.completedAt,
      status: d.status,
      environment: d.environment,
      ref: d.ref,
      commitSha: d.commitSha,
      blockedReason: d.blockedReason,
    })),
  ].sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());

  const hasInFlight = events.some(
    (e) => e.status === "running" || e.status === "pending" || e.status === "waiting_timer"
  );

  // Summary counts
  const gateTotal = gateRows.length;
  const gateFailed = gateRows.filter((g) => g.status === "failed").length;
  const gateRepaired = gateRows.filter((g) => g.repairSucceeded).length;
  const deployTotal = deployRows.length;
  const deployFailed = deployRows.filter((d) => d.status === "failed" || d.status === "blocked").length;

  const styles = `
    .pl-wrap { max-width: 920px; margin: 0 auto; padding: 0 var(--space-4); }

    .pl-summary {
      display: flex;
      gap: var(--space-3);
      flex-wrap: wrap;
      margin-bottom: var(--space-5);
      padding: var(--space-4);
      background: var(--bg-elevated);
      border: 1px solid var(--border);
      border-radius: 12px;
    }
    .pl-summary-stat {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 2px;
      min-width: 80px;
      padding: var(--space-2) var(--space-3);
      background: var(--bg-inset);
      border: 1px solid var(--border);
      border-radius: 10px;
    }
    .pl-summary-val {
      font-size: 22px;
      font-weight: 700;
      line-height: 1;
    }
    .pl-summary-val.ok   { color: #3fb950; }
    .pl-summary-val.err  { color: #f85149; }
    .pl-summary-val.warn { color: #d29922; }
    .pl-summary-val.neutral { color: var(--text); }
    .pl-summary-label { font-size: 11px; color: var(--text-muted); white-space: nowrap; }

    .pl-filter {
      display: flex;
      gap: var(--space-2);
      flex-wrap: wrap;
      margin-bottom: var(--space-4);
    }
    .pl-filter-btn {
      padding: 5px 14px;
      border: 1px solid var(--border);
      border-radius: 20px;
      background: var(--bg-inset);
      color: var(--text-secondary);
      font-size: 12px;
      font-weight: 600;
      cursor: pointer;
      text-decoration: none;
      transition: border-color 120ms, color 120ms;
    }
    .pl-filter-btn:hover, .pl-filter-btn.active {
      border-color: var(--accent);
      color: var(--accent);
    }

    .pl-timeline { position: relative; }
    .pl-timeline::before {
      content: '';
      position: absolute;
      left: 19px;
      top: 0;
      bottom: 0;
      width: 2px;
      background: var(--border);
    }

    .pl-event {
      position: relative;
      display: flex;
      gap: var(--space-3);
      padding: var(--space-3) 0;
    }
    .pl-event-dot {
      position: relative;
      z-index: 1;
      width: 20px;
      height: 20px;
      flex-shrink: 0;
      margin-top: 2px;
      border-radius: 50%;
      border: 2px solid var(--bg);
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 10px;
    }
    .pl-event-dot.gate-passed    { background: #3fb950; }
    .pl-event-dot.gate-failed    { background: #f85149; }
    .pl-event-dot.gate-running   { background: #58a6ff; }
    .pl-event-dot.gate-pending   { background: var(--text-muted); }
    .pl-event-dot.gate-skipped   { background: var(--border-strong); }
    .pl-event-dot.gate-repaired  { background: #d29922; }
    .pl-event-dot.deploy-success { background: #3fb950; }
    .pl-event-dot.deploy-failed  { background: #f85149; }
    .pl-event-dot.deploy-blocked { background: #d29922; }
    .pl-event-dot.deploy-running { background: #58a6ff; }
    .pl-event-dot.deploy-pending { background: var(--text-muted); }

    .pl-event-body {
      flex: 1;
      min-width: 0;
      background: var(--bg-elevated);
      border: 1px solid var(--border);
      border-radius: 10px;
      padding: var(--space-3) var(--space-4);
      transition: border-color 120ms;
    }
    .pl-event-body:hover { border-color: var(--border-strong); }

    .pl-event-top {
      display: flex;
      align-items: center;
      gap: var(--space-2);
      flex-wrap: wrap;
    }
    .pl-event-kind {
      font-size: 10px;
      font-weight: 700;
      text-transform: uppercase;
      letter-spacing: 0.08em;
      padding: 1px 7px;
      border-radius: 8px;
    }
    .pl-event-kind.gate   { background: rgba(88,166,255,0.12); color: #58a6ff; }
    .pl-event-kind.deploy { background: rgba(91,110,232,0.12); color: #8b9cf4; }
    .pl-event-name { font-size: 14px; font-weight: 600; color: var(--text); flex: 1; }
    .pl-event-time { font-size: 11px; color: var(--text-muted); white-space: nowrap; }
    .pl-event-status {
      font-size: 11px;
      font-weight: 600;
      padding: 2px 8px;
      border-radius: 8px;
    }
    .pl-status-passed   { background: rgba(63,185,80,0.12);  color: #3fb950; }
    .pl-status-failed   { background: rgba(248,81,73,0.12);  color: #f85149; }
    .pl-status-running  { background: rgba(88,166,255,0.12); color: #58a6ff; }
    .pl-status-pending  { background: rgba(130,80,223,0.10); color: #9a7fde; }
    .pl-status-skipped  { background: var(--bg-inset);       color: var(--text-muted); }
    .pl-status-repaired { background: rgba(210,153,34,0.12); color: #d29922; }
    .pl-status-success  { background: rgba(63,185,80,0.12);  color: #3fb950; }
    .pl-status-blocked  { background: rgba(210,153,34,0.12); color: #d29922; }

    .pl-event-meta {
      margin-top: var(--space-1);
      font-size: 12px;
      color: var(--text-muted);
      display: flex;
      gap: var(--space-3);
      flex-wrap: wrap;
      align-items: center;
    }
    .pl-event-meta a { color: var(--accent); text-decoration: none; }
    .pl-event-meta a:hover { text-decoration: underline; }
    .pl-sha {
      font-family: var(--font-mono);
      font-size: 11px;
      background: var(--bg-inset);
      padding: 1px 6px;
      border-radius: 5px;
    }
    .pl-repair-badge {
      font-size: 11px;
      font-weight: 600;
      padding: 1px 7px;
      border-radius: 8px;
      background: rgba(210,153,34,0.12);
      color: #d29922;
    }

    .pl-empty {
      text-align: center;
      padding: var(--space-8) var(--space-4);
      color: var(--text-muted);
      font-size: 14px;
    }

    @media (max-width: 600px) {
      .pl-wrap { padding: 0 var(--space-2); }
      .pl-summary { gap: var(--space-2); }
      .pl-event-body { padding: var(--space-2) var(--space-3); }
    }
  `;

  const gateStatusCls = (s: string) => `pl-status-${s}`;
  const deployStatusCls = (s: string) => {
    if (s === "success" || s === "succeeded") return "pl-status-success";
    return `pl-status-${s}`;
  };
  const dotCls = (e: TimelineEvent) => {
    if (e.kind === "gate") {
      const s = e.repairSucceeded ? "repaired" : e.status;
      return `gate-${s}`;
    }
    const s = e.status === "succeeded" ? "success" : e.status;
    return `deploy-${s}`;
  };

  return c.html(
    <Layout title={`Pipeline — ${owner}/${repo}`} user={authUser ?? undefined}>
      <style>{styles}</style>
      {hasInFlight && <meta http-equiv="refresh" content="15" />}

      <div class="pl-wrap">
        <RepoHeader
          owner={owner}
          repo={repo}
          starCount={repoRow.repo.starCount ?? 0}
          forkCount={repoRow.repo.forkCount ?? 0}
        />
        <RepoNav owner={owner} repo={repo} active="pipeline" />

        <div style="margin: var(--space-5) 0 var(--space-3); display:flex; align-items:baseline; gap:var(--space-3)">
          <h2 style="font-size:18px;font-weight:700;color:var(--text);margin:0">CI/CD Pipeline</h2>
          <span style="font-size:12px;color:var(--text-muted)">last 7 days</span>
          {hasInFlight && (
            <span style="font-size:11px;color:#58a6ff;margin-left:auto">⟳ auto-refresh every 15s</span>
          )}
        </div>

        {/* Summary bar */}
        <div class="pl-summary">
          <div class="pl-summary-stat">
            <span class={`pl-summary-val ${gateFailed === 0 ? "ok" : "err"}`}>{gateFailed}</span>
            <span class="pl-summary-label">Gate failures</span>
          </div>
          <div class="pl-summary-stat">
            <span class={`pl-summary-val neutral`}>{gateTotal}</span>
            <span class="pl-summary-label">Gate runs</span>
          </div>
          <div class="pl-summary-stat">
            <span class={`pl-summary-val ${gateRepaired > 0 ? "warn" : "neutral"}`}>{gateRepaired}</span>
            <span class="pl-summary-label">AI-repaired</span>
          </div>
          <div class="pl-summary-stat">
            <span class={`pl-summary-val ${deployFailed === 0 ? "ok" : "err"}`}>{deployFailed}</span>
            <span class="pl-summary-label">Deploy issues</span>
          </div>
          <div class="pl-summary-stat">
            <span class={`pl-summary-val neutral`}>{deployTotal}</span>
            <span class="pl-summary-label">Deployments</span>
          </div>
        </div>

        {events.length === 0 ? (
          <div class="pl-empty">
            <div style="font-size:32px;margin-bottom:var(--space-3)">🚀</div>
            <div style="font-weight:600;color:var(--text);margin-bottom:var(--space-2)">No pipeline events yet</div>
            <div>Gate runs and deployments will appear here after your first push.</div>
          </div>
        ) : (
          <div class="pl-timeline">
            {events.map((e) => (
              <div class="pl-event" key={e.id}>
                <div class={`pl-event-dot ${dotCls(e)}`} />
                <div class="pl-event-body">
                  <div class="pl-event-top">
                    <span class={`pl-event-kind ${e.kind}`}>
                      {e.kind === "gate" ? "Gate" : "Deploy"}
                    </span>
                    <span class="pl-event-name">
                      {e.kind === "gate" ? e.gateName : `${e.environment} deployment`}
                    </span>
                    {e.kind === "gate" && e.repairSucceeded && (
                      <span class="pl-repair-badge">⚡ AI-repaired</span>
                    )}
                    <span class={`pl-event-status ${e.kind === "gate" ? gateStatusCls(e.repairSucceeded ? "repaired" : e.status) : deployStatusCls(e.status)}`}>
                      {e.kind === "gate"
                        ? (e.repairSucceeded ? "repaired" : e.status)
                        : (e.status === "succeeded" ? "success" : e.status)}
                    </span>
                    <span class="pl-event-time">{relTime(e.createdAt)}</span>
                  </div>
                  <div class="pl-event-meta">
                    <span class="pl-sha">{e.commitSha.slice(0, 8)}</span>
                    <span>{e.ref.replace("refs/heads/", "")}</span>
                    {e.kind === "gate" && e.prNumber && (
                      <a href={`/${owner}/${repo}/pulls/${e.prNumber}`}>
                        PR #{e.prNumber}
                      </a>
                    )}
                    {e.kind === "gate" && e.durationMs && (
                      <span>{durationStr(0, e.durationMs)}</span>
                    )}
                    {e.kind === "gate" && !e.durationMs && e.completedAt && (
                      <span>{durationStr(e.createdAt.getTime(), e.completedAt.getTime())}</span>
                    )}
                    {e.kind === "deploy" && e.blockedReason && (
                      <span style="color:var(--warning)">{e.blockedReason.slice(0, 80)}</span>
                    )}
                    {e.kind === "deploy" && (
                      <a href={`/${owner}/${repo}/cloud-deployments`}>
                        Details →
                      </a>
                    )}
                  </div>
                </div>
              </div>
            ))}
          </div>
        )}
      </div>
    </Layout>
  );
});

export default app;
