// =============================================================================
// HeadlineSift.com — Admin Dashboard
// =============================================================================
// Server component — queries SQLite directly via Prisma.
// All data is real, no hardcoded stats.

import type { Metadata } from "next";
import { prisma } from "@/lib/db/client";

export const metadata: Metadata = { title: "Dashboard" };

// ============================================================================
// Types
// ============================================================================

type ChangeDirection = "up" | "down" | "neutral";

interface StatCard {
  label: string;
  value: string;
  sub?: string;
  direction: ChangeDirection;
}

interface FetchFailure {
  id: string;
  sourceName: string;
  errorMessage: string | null;
  startedAt: Date;
}

interface FailedJob {
  id: string;
  type: string;
  errorMessage: string | null;
  completedAt: Date | null;
}

interface ErrorSource {
  id: string;
  name: string;
  status: string;
  lastErrorMessage: string | null;
  consecutiveFailures: number;
}

interface TopCategory {
  id: string;
  name: string;
  slug: string;
  storyCount: number;
}

// ============================================================================
// Helpers
// ============================================================================

function startOfToday(): Date {
  const d = new Date();
  d.setHours(0, 0, 0, 0);
  return d;
}

function ago(date: Date): string {
  const diffMs = Date.now() - date.getTime();
  const mins = Math.floor(diffMs / 60_000);
  if (mins < 1) {
    return "just now";
  }
  if (mins < 60) {
    return `${mins}m ago`;
  }
  const hours = Math.floor(mins / 60);
  if (hours < 24) {
    return `${hours}h ago`;
  }
  const days = Math.floor(hours / 24);
  return `${days}d ago`;
}

function fmtNum(n: number): string {
  if (n >= 1_000_000) {
    return `${(n / 1_000_000).toFixed(1)}M`;
  }
  if (n >= 1_000) {
    return `${(n / 1_000).toFixed(1)}K`;
  }
  return String(n);
}

// ============================================================================
// Page
// ============================================================================

export default async function AdminDashboard() {
  const today = startOfToday();

  // ---- Parallel data fetch (single round-trip to SQLite) ----
  const [
    totalSources,
    activeSources,
    errorSourcesCount,
    articlesToday,
    duplicatesToday,
    clustersToday,
    aiSummariesToday,
    pendingReview,
    publishedToday,
    pendingJobs,
    failedJobsCount,
    lastBackup,
    recentFetchFailures,
    recentFailedJobs,
    errorSources,
    topCategoriesRaw,
  ] = await Promise.all([
    // --- Counts ---
    prisma.source.count(),
    prisma.source.count({ where: { status: "ACTIVE" } }),
    prisma.source.count({ where: { status: { in: ["ERROR", "RATE_LIMITED"] } } }),
    prisma.rawArticle.count({
      where: { fetchedAt: { gte: today } },
    }),
    // Duplicates today = sum of duplicatesFound from today's fetch logs
    prisma.fetchLog.aggregate({
      where: { startedAt: { gte: today } },
      _sum: { duplicatesFound: true },
    }),
    prisma.storyCluster.count({
      where: { createdAt: { gte: today } },
    }),
    prisma.aiStoryAnalysis.count({
      where: { generatedAt: { gte: today } },
    }),
    prisma.storyCluster.count({
      where: { status: "NEEDS_REVIEW" },
    }),
    prisma.storyCluster.count({
      where: { status: "PUBLISHED", updatedAt: { gte: today } },
    }),
    prisma.job.count({ where: { status: "PENDING" } }),
    prisma.job.count({ where: { status: "FAILED" } }),

    // --- Last backup ---
    prisma.job.findFirst({
      where: { type: "BACKUP_DATABASE", status: "COMPLETED" },
      orderBy: { completedAt: "desc" },
      select: { completedAt: true },
    }),

    // --- Recent fetch failures (last 5) ---
    prisma.fetchLog.findMany({
      where: { status: "FAILED" },
      orderBy: { startedAt: "desc" },
      take: 5,
      select: {
        id: true,
        errorMessage: true,
        startedAt: true,
        source: { select: { name: true } },
      },
    }),

    // --- Recent failed jobs (last 5) ---
    prisma.job.findMany({
      where: { status: "FAILED" },
      orderBy: { completedAt: "desc" },
      take: 5,
      select: {
        id: true,
        type: true,
        errorMessage: true,
        completedAt: true,
      },
    }),

    // --- Sources with errors ---
    prisma.source.findMany({
      where: { status: { in: ["ERROR", "RATE_LIMITED"] } },
      select: {
        id: true,
        name: true,
        status: true,
        lastErrorMessage: true,
        consecutiveFailures: true,
      },
      orderBy: { consecutiveFailures: "desc" },
      take: 10,
    }),

    // --- Top categories by story count ---
    prisma.storyCluster.groupBy({
      by: ["categoryId"],
      _count: { id: true },
      orderBy: { _count: { id: "desc" } },
      take: 5,
    }),
  ]);

  // Resolve category names for the top-categories query
  const categoryIds = topCategoriesRaw
    .map((c) => c.categoryId)
    .filter((id): id is string => id !== null);
  const categories =
    categoryIds.length > 0
      ? await prisma.category.findMany({
          where: { id: { in: categoryIds } },
          select: { id: true, name: true, slug: true },
        })
      : [];
  const categoryMap = new Map(categories.map((c) => [c.id, c]));

  const topCategories: TopCategory[] = topCategoriesRaw
    .filter((c): c is typeof c & { categoryId: string } => c.categoryId !== null)
    .map((c) => {
      const cat = categoryMap.get(c.categoryId);
      return {
        id: c.categoryId,
        name: cat?.name ?? "Unknown",
        slug: cat?.slug ?? "",
        storyCount: c._count.id,
      };
    });

  // Transform data for components
  const failures: FetchFailure[] = recentFetchFailures.map((f) => ({
    id: f.id,
    sourceName: f.source.name,
    errorMessage: f.errorMessage,
    startedAt: f.startedAt,
  }));

  const failedJobs: FailedJob[] = recentFailedJobs.map((j) => ({
    id: j.id,
    type: j.type,
    errorMessage: j.errorMessage,
    completedAt: j.completedAt,
  }));

  const errorSourceList: ErrorSource[] = errorSources.map((s) => ({
    id: s.id,
    name: s.name,
    status: s.status,
    lastErrorMessage: s.lastErrorMessage,
    consecutiveFailures: s.consecutiveFailures,
  }));

  const duplicatesTodayCount = duplicatesToday._sum.duplicatesFound ?? 0;

  // ---- Build stats ----
  const stats: StatCard[] = [
    {
      label: "Total Sources",
      value: fmtNum(totalSources),
      sub: `${fmtNum(activeSources)} active`,
      direction: "neutral",
    },
    {
      label: "Failed Sources",
      value: fmtNum(errorSourcesCount),
      sub:
        errorSourcesCount > 0
          ? `${errorSourcesCount} need attention`
          : "All healthy",
      direction: errorSourcesCount > 0 ? "down" : "up",
    },
    {
      label: "Articles Today",
      value: fmtNum(articlesToday),
      sub: `${fmtNum(duplicatesTodayCount)} duplicates`,
      direction: "neutral",
    },
    {
      label: "Clusters Today",
      value: fmtNum(clustersToday),
      sub: `${fmtNum(aiSummariesToday)} AI summaries`,
      direction: "neutral",
    },
    {
      label: "Pending Review",
      value: fmtNum(pendingReview),
      sub:
        pendingReview > 0
          ? "Awaiting approval"
          : "Queue empty",
      direction: pendingReview > 10 ? "down" : "up",
    },
    {
      label: "Published Today",
      value: fmtNum(publishedToday),
      sub: "Stories live",
      direction: "neutral",
    },
    {
      label: "Pending Jobs",
      value: fmtNum(pendingJobs),
      sub:
        pendingJobs > 0
          ? "In queue"
          : "No pending jobs",
      direction: pendingJobs > 5 ? "down" : "up",
    },
    {
      label: "Failed Jobs",
      value: fmtNum(failedJobsCount),
      sub:
        failedJobsCount > 0
          ? "Need investigation"
          : "All clear",
      direction: failedJobsCount > 0 ? "down" : "up",
    },
    {
      label: "Last Backup",
      value: lastBackup?.completedAt
        ? ago(lastBackup.completedAt)
        : "Never",
      sub: lastBackup?.completedAt
        ? lastBackup.completedAt.toLocaleDateString()
        : "Run a backup",
      direction: lastBackup ? "up" : "down",
    },
  ];

  const hasAnyData =
    totalSources > 0 ||
    articlesToday > 0 ||
    clustersToday > 0 ||
    pendingJobs > 0;

  return (
    <div className="animate-fade-in space-y-8">
      {/* Page Header */}
      <div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
        <div>
          <h1 className="text-2xl font-bold">Dashboard</h1>
          <p className="mt-1 text-sm text-body-muted">
            Real-time overview of your news aggregation pipeline.
          </p>
        </div>
        {!hasAnyData && (
          <span className="inline-flex items-center gap-1.5 self-start rounded-full bg-amber-50 px-3 py-1 text-xs font-medium text-amber-700 sm:self-center">
            <span className="h-1.5 w-1.5 rounded-full bg-amber-500" />
            Empty database — run seed &amp; fetch
          </span>
        )}
      </div>

      {/* ---- Stats Grid ---- */}
      {!hasAnyData ? (
        <EmptyDashboard />
      ) : (
        <>
          <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
            {stats.map((stat) => (
              <StatCard key={stat.label} stat={stat} />
            ))}
          </div>

          {/* ---- Recent Issues (side by side on lg+) ---- */}
          <div className="grid gap-6 lg:grid-cols-2">
            {/* Fetch Failures */}
            <div className="card">
              <h2 className="mb-4 text-sm font-semibold uppercase tracking-wide text-body-muted">
                Recent Fetch Failures
              </h2>
              {failures.length === 0 ? (
                <EmptyList message="No recent fetch failures — all sources are healthy." />
              ) : (
                <ul className="divide-y divide-border">
                  {failures.map((f) => (
                    <li key={f.id} className="py-3 first:pt-0 last:pb-0">
                      <div className="flex items-start justify-between gap-2">
                        <div className="min-w-0">
                          <p className="text-sm font-medium text-heading truncate">
                            {f.sourceName}
                          </p>
                          <p className="mt-0.5 text-xs text-body-muted line-clamp-2">
                            {f.errorMessage ?? "Unknown error"}
                          </p>
                        </div>
                        <span className="shrink-0 text-2xs text-body-muted">
                          {ago(f.startedAt)}
                        </span>
                      </div>
                    </li>
                  ))}
                </ul>
              )}
            </div>

            {/* Failed Jobs */}
            <div className="card">
              <h2 className="mb-4 text-sm font-semibold uppercase tracking-wide text-body-muted">
                Recent Failed Jobs
              </h2>
              {failedJobs.length === 0 ? (
                <EmptyList message="No recent failed jobs — everything is running smoothly." />
              ) : (
                <ul className="divide-y divide-border">
                  {failedJobs.map((j) => (
                    <li key={j.id} className="py-3 first:pt-0 last:pb-0">
                      <div className="flex items-start justify-between gap-2">
                        <div className="min-w-0">
                          <p className="text-sm font-medium text-heading">
                            <JobTypeBadge type={j.type} />
                          </p>
                          <p className="mt-0.5 text-xs text-body-muted line-clamp-2">
                            {j.errorMessage ?? "Unknown error"}
                          </p>
                        </div>
                        <span className="shrink-0 text-2xs text-body-muted">
                          {j.completedAt ? ago(j.completedAt) : "—"}
                        </span>
                      </div>
                    </li>
                  ))}
                </ul>
              )}
            </div>
          </div>

          {/* ---- Bottom Row: Error Sources + Top Categories ---- */}
          <div className="grid gap-6 lg:grid-cols-2">
            {/* Sources with Errors */}
            <div className="card">
              <h2 className="mb-4 text-sm font-semibold uppercase tracking-wide text-body-muted">
                Sources with Errors
              </h2>
              {errorSourceList.length === 0 ? (
                <EmptyList message="No sources in error state." />
              ) : (
                <ul className="divide-y divide-border">
                  {errorSourceList.map((s) => (
                    <li key={s.id} className="py-3 first:pt-0 last:pb-0">
                      <div className="flex items-center justify-between gap-2">
                        <div className="min-w-0">
                          <p className="text-sm font-medium text-heading truncate">
                            {s.name}
                          </p>
                          <p className="mt-0.5 text-xs text-body-muted truncate">
                            {s.lastErrorMessage ?? "No error details"}
                          </p>
                        </div>
                        <div className="flex items-center gap-2 shrink-0">
                          <span
                            className={`badge text-2xs ${
                              s.status === "RATE_LIMITED"
                                ? "badge-trending"
                                : "badge-breaking"
                            }`}
                          >
                            {s.status.replace(/_/g, " ")}
                          </span>
                          <span className="text-2xs text-body-muted">
                            ×{s.consecutiveFailures}
                          </span>
                        </div>
                      </div>
                    </li>
                  ))}
                </ul>
              )}
            </div>

            {/* Top Categories by Story Count */}
            <div className="card">
              <h2 className="mb-4 text-sm font-semibold uppercase tracking-wide text-body-muted">
                Top Categories by Story Count
              </h2>
              {topCategories.length === 0 ? (
                <EmptyList message="No story clusters yet — run the pipeline to generate stories." />
              ) : (
                <ul className="divide-y divide-border">
                  {topCategories.map((cat, i) => (
                    <li
                      key={cat.id}
                      className="flex items-center gap-3 py-3 first:pt-0 last:pb-0"
                    >
                      <span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-brand-50 text-xs font-bold text-brand-700">
                        {i + 1}
                      </span>
                      <div className="flex-1 min-w-0">
                        <p className="text-sm font-medium text-heading truncate">
                          {cat.name}
                        </p>
                      </div>
                      <span className="shrink-0 text-sm font-semibold text-heading">
                        {fmtNum(cat.storyCount)}
                      </span>
                    </li>
                  ))}
                </ul>
              )}
            </div>
          </div>
        </>
      )}
    </div>
  );
}

// ============================================================================
// Sub-components
// ============================================================================

function StatCard({ stat }: { stat: StatCard }) {
  const directionColor =
    stat.direction === "up"
      ? "text-green-600"
      : stat.direction === "down"
        ? "text-red-600"
        : "text-body-muted";

  return (
    <div className="card">
      <p className="text-xs font-medium uppercase tracking-wide text-body-muted">
        {stat.label}
      </p>
      <p className="mt-1 text-2xl font-bold text-heading">{stat.value}</p>
      {stat.sub && (
        <p className={`mt-1 text-xs ${directionColor}`}>{stat.sub}</p>
      )}
    </div>
  );
}

function EmptyList({ message }: { message: string }) {
  return (
    <div className="flex flex-col items-center justify-center py-10 text-center">
      <svg
        className="mb-2 h-8 w-8 text-body-muted/40"
        fill="none"
        viewBox="0 0 24 24"
        stroke="currentColor"
        strokeWidth={1.5}
      >
        <path
          strokeLinecap="round"
          strokeLinejoin="round"
          d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
        />
      </svg>
      <p className="text-sm text-body-muted">{message}</p>
    </div>
  );
}

function JobTypeBadge({ type }: { type: string }) {
  const label = type
    .replace(/_/g, " ")
    .toLowerCase()
    .replace(/\b\w/g, (c) => c.toUpperCase());

  return (
    <span className="inline-flex items-center gap-1">
      <span className="h-1.5 w-1.5 rounded-full bg-red-500" />
      {label}
    </span>
  );
}

function EmptyDashboard() {
  return (
    <div className="card">
      <div className="flex flex-col items-center justify-center py-20 text-center">
        <span className="text-6xl" aria-hidden="true">
          🌱
        </span>
        <h2 className="mt-6 text-xl font-bold text-heading">
          Welcome to HeadlineSift
        </h2>
        <p className="mt-3 max-w-lg text-sm text-body-muted">
          Your database is empty. Follow the setup checklist below to get your
          news aggregation pipeline running.
        </p>

        <div className="mt-8 w-full max-w-md space-y-3 text-left">
          {SETUP_STEPS.map((step, i) => (
            <div
              key={step.label}
              className="flex items-start gap-3 rounded-md border border-border bg-surface-secondary p-3"
            >
              <span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-brand-600 text-xs font-bold text-white">
                {i + 1}
              </span>
              <div>
                <p className="text-sm font-medium text-heading">{step.label}</p>
                <code className="mt-1 block text-xs text-body-muted">
                  {step.command}
                </code>
              </div>
            </div>
          ))}
        </div>

        <p className="mt-8 text-xs text-body-muted">
          After seeding, return here to see live pipeline statistics.
        </p>
      </div>
    </div>
  );
}

const SETUP_STEPS = [
  {
    label: "Set environment variables",
    command: "cp .env.example .env   # then edit .env with your values",
  },
  {
    label: "Install dependencies & generate Prisma client",
    command: "npm install && npm run db:generate",
  },
  {
    label: "Push schema to SQLite & seed initial data",
    command: "npm run db:push && npm run db:seed",
  },
  {
    label: "Start the dev server",
    command: "npm run dev",
  },
];
