/**
 * Overview — the landing view.
 *
 * Leads with margin and idle capacity rather than deal counts, because those
 * are the numbers this business actually turns on. A CRM that opens on
 * "23 open opportunities" tells you nothing about whether you are making money.
 *
 * The one thing that outranks the money is the licence to operate. An export
 * authorisation nobody renewed converts lawful business into unlawful business,
 * and the Calendar — where every other dated risk lives — can only ever report
 * the quarter being read. So a lapse is reported here, in the first screenful,
 * however long ago it happened.
 */
import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { AlertTriangle, ArrowRight, Plus, Server, ShieldAlert } from 'lucide-react';
import { Link } from 'react-router-dom';
import {
  compactNumber,
  get,
  money,
  percent,
  relativeTime,
  shortDate,
  unitPrice,
} from '@/lib/api';
import {
  Badge,
  Button,
  Card,
  CardContent,
  CardHeader,
  CardTitle,
  EmptyState,
  Skeleton,
  Stat,
  cn,
} from '@/components/ui';
import { withoutDemoPrefix } from '@/lib/utils';
import { CommitmentSheet } from '@/components/RecordSheets';
import { usePageTitle } from '@/lib/title';
import { usePiggyContext } from '@/lib/piggy-context';
import { useIdentity } from '@/lib/identity';
import { can } from '@/lib/permissions';

interface ComplianceItem {
  id: string;
  kind: 'authorization' | 'artifact';
  /** The specific instrument — "Export licence", "SOC 2" — not the table it came from. */
  label: string;
  reference: string | null;
  accountId: string | null;
  accountName: string | null;
  expiresAt: string;
  lapsed: boolean;
  volatile: boolean;
  href: string;
}

interface Dashboard {
  me: { name: string; teams: { team: string; role: string }[] };
  margin: {
    revenueCents: number;
    costCents: number;
    grossMarginCents: number;
    grossMarginPct: number | null;
    marginPerAllocatedGpuHourCents: number | null;
    utilisation: number;
    idleGpuHours: number;
    committedGpuHours: number;
    allocatedGpuHours: number;
  };
  blocks: number;
  openDemandDeals: number;
  openDemandAcvCents: number;
  openSupplyDeals: number;
  compliance: {
    horizonDays: number;
    lapsedCount: number;
    expiringCount: number;
    items: ComplianceItem[];
  };
  idleAlerts: {
    commitmentId: string;
    name: string;
    gpuType: string;
    gpuCount: number;
    idleCostCents: number;
    utilisation: number;
    breakEvenPriceCents: number | null;
  }[];
  recentActivity: {
    id: string;
    type: string;
    subject: string | null;
    accountName: string | null;
    occurredAt: string;
  }[];
}

export function Overview() {
  usePageTitle('Overview');
  const me = useIdentity();
  const [recordingCommitment, setRecordingCommitment] = useState(false);
  const { data, isLoading, error, refetch } = useQuery({
    queryKey: ['dashboard'],
    queryFn: () => get<Dashboard>('/api/dashboard'),
    // The book does not change second to second, but it does change while
    // someone is looking at it during a pipeline review.
    refetchInterval: 60_000,
  });

  /*
   * Published unconditionally — hooks cannot hide behind the loading return
   * below — and labelled from the figures once they arrive, so the dock names
   * the book the reader is looking at rather than repeating the route.
   */
  usePiggyContext({
    type: 'page',
    route: '/',
    ...(data
      ? {
          label: `Overview — ${percent(data.margin.grossMarginPct, 1)} margin, ${percent(
            data.margin.utilisation,
          )} of committed capacity sold`,
        }
      : {}),
  });

  if (isLoading) {
    return (
      <div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
        {Array.from({ length: 4 }).map((_, i) => (
          <Skeleton key={i} className="h-28" />
        ))}
      </div>
    );
  }

  if (error || !data) {
    return (
      <Card>
        <CardContent className="flex flex-col items-center gap-4 pt-6">
          <EmptyState
            title="Could not load the overview"
            description={error instanceof Error ? error.message : 'Unknown error.'}
          />
          <Button variant="outline" onClick={() => void refetch()}>Try again</Button>
        </CardContent>
      </Card>
    );
  }

  const m = data.margin;
  const marginTone = m.grossMarginCents >= 0 ? 'positive' : 'danger';
  // The seeded book prefixes every name with its demonstration marker, and a
  // positional read of that took the label for the person: "Good evening,
  // DEMO" was the first line of the screen everyone opens.
  const firstName = withoutDemoPrefix(data.me.name).split(' ')[0];
  const idleExposureCents = data.idleAlerts.reduce((sum, alert) => sum + alert.idleCostCents, 0);
  /*
   * Recording what capacity was bought is a supply lead's authority, not
   * everyone's. Offering the button to someone the server will refuse turns an
   * empty screen into a 403, which is a worse dead end than the one it fixes.
   */
  const canRecordCommitment = can(me, 'commitment:write', 'supply');

  return (
    <div className="space-y-5 pb-[calc(5.5rem+var(--safe-bottom))] md:pb-0">
      <header>
        <h1 className="text-xl font-semibold tracking-tight sm:text-2xl">
          {greeting()}, {firstName}
        </h1>
        <p className="mt-1 text-sm text-muted">
          {data.blocks === 0
            ? 'No capacity commitments yet — margin appears once you record what you have bought.'
            : `${data.blocks} capacity commitment${data.blocks === 1 ? '' : 's'} on the book.`}
        </p>
      </header>

      <section className="grid grid-cols-2 gap-2 sm:gap-3 xl:grid-cols-4">
        <Stat
          label="Gross margin"
          value={money(m.grossMarginCents)}
          // "— of $0 revenue" is what a percentage of nothing prints, and it
          // reads as a broken figure rather than an empty book.
          hint={
            m.revenueCents === 0
              ? 'Nothing sold yet'
              : `${percent(m.grossMarginPct, 1)} of ${money(m.revenueCents)} revenue`
          }
          tone={marginTone}
        />
        <Stat
          label="Sold ratio"
          value={percent(m.utilisation, 1)}
          hint={`${compactNumber(m.allocatedGpuHours)} of ${compactNumber(m.committedGpuHours)} GPU-hrs sold`}
          // Nothing bought cannot be under-sold; warning on 0% of 0 hours is an
          // alarm about a book that does not exist yet.
          tone={m.committedGpuHours > 0 && m.utilisation < 0.6 ? 'warning' : 'default'}
        />
        <Stat
          label="Idle capacity"
          value={`${compactNumber(m.idleGpuHours)} hrs`}
          hint="Bought and unsold"
          tone={m.idleGpuHours > 0 ? 'warning' : 'default'}
        />
        {/*
          The value of what is open rather than a count of it — a count is the
          one figure on this page nobody can act on. The money is demand ACV
          alone, because a supply deal carries GPUs and a target cost and never
          a contract value; both counts stay in the hint, where they now agree
          with the two pipeline boards.
        */}
        <Stat
          label="Open pipeline"
          value={money(data.openDemandAcvCents)}
          hint={`${data.openDemandDeals} demand · ${data.openSupplyDeals} supply open`}
        />
      </section>

      <LicenceToOperate compliance={data.compliance} />

      {data.idleAlerts.length > 0 ? (
        <Card className="border-warning/30">
          <CardHeader className="flex-row items-start justify-between gap-3 space-y-0">
            <div className="flex min-w-0 items-start gap-2">
              <AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-warning" aria-hidden />
              <div>
                <CardTitle className="text-base">Capacity you are paying for and not selling</CardTitle>
                <p className="mt-1 text-xs text-muted">Prioritized by idle cost exposure.</p>
              </div>
            </div>
            <Badge tone="warning" className="nums shrink-0">{money(idleExposureCents)}</Badge>
          </CardHeader>
          <CardContent className="space-y-2">
            {data.idleAlerts.map((alert) => (
              <div
                key={alert.commitmentId}
                className="flex flex-col gap-2 rounded-lg bg-surface-2 p-3 sm:flex-row sm:items-center sm:justify-between"
              >
                <div className="min-w-0">
                  <p className="truncate font-medium">{alert.name}</p>
                  <p className="text-xs text-muted">
                    {alert.gpuCount}× {alert.gpuType} · {percent(alert.utilisation)} sold
                    {/*
                      A zero break-even means the block's cost is already
                      covered, so any further sale is upside. Printing
                      "break even above $0.00" is technically true and reads
                      like a bug, so it is said in words instead.
                    */}
                    {alert.breakEvenPriceCents == null ? null : alert.breakEvenPriceCents > 0 ? (
                      <>
                        {' · '}break even above{' '}
                        <span className="nums">{unitPrice(alert.breakEvenPriceCents)}</span>/GPU-hr
                      </>
                    ) : (
                      <>{' · '}cost already covered — further sales are upside</>
                    )}
                  </p>
                </div>
                <div className="flex items-center gap-3 sm:justify-end">
                  <span className="nums whitespace-nowrap text-sm font-semibold text-warning">
                    {money(alert.idleCostCents)}
                  </span>
                  <Link
                    to="/capacity"
                    className="tap inline-flex min-h-11 items-center gap-1 rounded-lg px-3 text-sm font-medium text-accent-fg hover:bg-surface"
                    aria-label={`Match demand to ${alert.name}`}
                  >
                    Match
                    <ArrowRight className="h-3.5 w-3.5" aria-hidden />
                  </Link>
                </div>
              </div>
            ))}
          </CardContent>
        </Card>
      ) : null}

      {/* `items-start` so a short book does not stretch to the height of a busy
          activity feed and open a hole under its last row. */}
      <div className="grid items-start gap-4 lg:grid-cols-2">
        <Card>
          <CardHeader>
            <CardTitle className="text-base">The book</CardTitle>
          </CardHeader>
          <CardContent className="space-y-2 text-sm">
            <Row label="Revenue" value={money(m.revenueCents)} />
            <Row label="Cost of committed capacity" value={money(m.costCents)} />
            <div className="space-y-2 border-t border-border pt-2">
              <Row
                label="Gross margin"
                value={money(m.grossMarginCents)}
                emphasis
                tone={marginTone}
              />
              {/*
                The blended rate, which is what a block is actually judged on:
                a book can clear millions and still be selling GPU-hours for
                pennies over what they cost.
              */}
              <Row
                label="Margin per GPU-hour sold"
                value={unitPrice(m.marginPerAllocatedGpuHourCents)}
              />
            </div>
            <p className="pt-2 text-xs leading-relaxed text-muted">
              Cost is charged against the full commitment, not only the hours that sold —
              unsold hours are already paid for.
            </p>
          </CardContent>
        </Card>

        <Card>
          <CardHeader>
            <CardTitle className="text-base">Recent activity</CardTitle>
          </CardHeader>
          <CardContent>
            {data.recentActivity.length === 0 ? (
              <p className="py-6 text-center text-sm text-muted">Nothing logged yet.</p>
            ) : (
              <ul className="space-y-2.5">
                {data.recentActivity.slice(0, 6).map((activity) => (
                  <li key={activity.id} className="flex items-start gap-2 text-sm">
                    <Badge tone="neutral" className="mt-0.5 shrink-0">
                      {activity.type.replace('_', ' ')}
                    </Badge>
                    <span className="min-w-0 flex-1">
                      <span className="block truncate">{activity.subject ?? '—'}</span>
                      {activity.accountName ? (
                        <span className="block truncate text-xs text-muted">
                          {activity.accountName}
                        </span>
                      ) : null}
                    </span>
                    <span className="shrink-0 text-xs text-muted">
                      {relativeTime(activity.occurredAt)}
                    </span>
                  </li>
                ))}
              </ul>
            )}
          </CardContent>
        </Card>
      </div>

      {data.blocks === 0 ? (
        <Card>
          <CardContent className="pt-5">
            <EmptyState
              icon={<Server className="h-8 w-8" />}
              title="No capacity on the book yet"
              description={
                canRecordCommitment
                  ? 'Record a capacity commitment — what you bought, at what cost, over what term — and margin, utilisation and idle alerts all follow from it.'
                  : 'Margin, utilisation and idle alerts all follow from a recorded capacity commitment. Recording one needs supply-lead authority — ask a supply lead to add the first block.'
              }
              action={
                canRecordCommitment ? (
                  <Button variant="primary" onClick={() => setRecordingCommitment(true)}>
                    <Plus data-icon="inline-start" aria-hidden />
                    Record a capacity commitment
                  </Button>
                ) : null
              }
            />
          </CardContent>
        </Card>
      ) : null}

      {/*
        Mounted outside the empty state it is opened from: recording the first
        block makes `blocks` non-zero, and a sheet that unmounts underneath its
        own success toast closes with a jump.
      */}
      <CommitmentSheet
        open={recordingCommitment}
        onOpenChange={setRecordingCommitment}
        identity={me}
      />
    </div>
  );
}

/**
 * The compliance tile.
 *
 * Always rendered, never collapsed to nothing when the news is good: a card
 * that appears only in trouble teaches the reader that its absence means
 * nothing was checked. The tone escalates instead — quiet when clear, warning
 * inside the horizon, and danger the moment anything has lapsed.
 */
function LicenceToOperate({ compliance }: { compliance: Dashboard['compliance'] }) {
  const lapsed = compliance.lapsedCount > 0;
  const expiring = compliance.expiringCount > 0;
  /*
   * Three rows, however many are dated. The server sorts lapsed first, so the
   * rows that survive the cut are never the ones this card exists for; the
   * remainder is a queue, and a queue belongs on the Calendar's compliance
   * lane rather than on the screen everyone opens first.
   */
  const shown = compliance.items.slice(0, COMPLIANCE_ROWS);
  const remaining = compliance.lapsedCount + compliance.expiringCount - shown.length;
  const hiddenLapsed = compliance.lapsedCount - shown.filter((item) => item.lapsed).length;

  return (
    <Card className={cn(lapsed && 'border-danger/50', !lapsed && expiring && 'border-warning/30')}>
      <CardHeader className="flex-row items-start justify-between gap-3 space-y-0">
        <div className="flex min-w-0 items-start gap-2">
          <ShieldAlert
            className={cn(
              'mt-0.5 h-4 w-4 shrink-0',
              lapsed ? 'text-danger' : expiring ? 'text-warning' : 'text-muted',
            )}
            aria-hidden
          />
          <div>
            <CardTitle className="text-base">Licence to operate</CardTitle>
            <p className="mt-1 text-xs text-muted">
              {lapsed
                ? 'An expired export authorisation converts lawful business into unlawful business.'
                : `Export authorisations and attestations expiring within ${compliance.horizonDays} days.`}
            </p>
          </div>
        </div>
        <Badge
          tone={lapsed ? 'danger' : expiring ? 'warning' : 'positive'}
          className="nums shrink-0"
        >
          {lapsed
            ? `${compliance.lapsedCount} lapsed`
            : expiring
              ? `${compliance.expiringCount} expiring`
              : 'Clear'}
        </Badge>
      </CardHeader>
      <CardContent className="space-y-2">
        {shown.length === 0 ? (
          <p className="text-sm leading-relaxed text-muted">
            <span className="text-fg">Nothing on file has lapsed</span>, and nothing expires in the
            next {compliance.horizonDays} days. A counterparty with no authorisation recorded at all
            is not covered by this check.
          </p>
        ) : (
          shown.map((item) => <ComplianceRow key={item.id} item={item} />)
        )}
        {remaining > 0 ? (
          <Link
            to="/calendar"
            className="tap inline-flex min-h-11 items-center gap-1 text-sm font-medium text-accent-fg"
          >
            {hiddenLapsed > 0
              ? `${remaining} more, ${hiddenLapsed} of them lapsed`
              : `${remaining} more expiring within ${compliance.horizonDays} days`}
            <ArrowRight className="h-3.5 w-3.5" aria-hidden />
          </Link>
        ) : null}
      </CardContent>
    </Card>
  );
}

function ComplianceRow({ item }: { item: ComplianceItem }) {
  const days = daysUntil(item.expiresAt);
  const detail = [
    item.kind === 'authorization' ? 'Export authorisation' : 'Compliance artefact',
    item.reference,
    // The date on file cannot be trusted for this counterparty; say so where
    // the deadline is read, not on a screen nobody opens.
    item.volatile ? 'rules in flux, re-verify' : null,
  ].filter((part): part is string => Boolean(part));

  return (
    <div className="flex flex-col gap-2 rounded-lg bg-surface-2 p-3 sm:flex-row sm:items-center sm:justify-between">
      <div className="min-w-0">
        <p className="truncate font-medium">
          {item.label}
          {item.accountName ? <span className="text-muted"> — {item.accountName}</span> : null}
        </p>
        <p className="truncate text-xs text-muted">{detail.join(' · ')}</p>
      </div>
      <div className="flex items-center gap-3 sm:justify-end">
        <span
          className={cn(
            'nums whitespace-nowrap text-sm font-semibold',
            item.lapsed ? 'text-danger' : 'text-warning',
          )}
        >
          {item.lapsed
            ? `Lapsed ${shortDate(item.expiresAt)} · ${Math.abs(days)}d ago`
            : `Expires ${shortDate(item.expiresAt)} · ${days}d`}
        </span>
        <Link
          to={item.href}
          className="tap inline-flex min-h-11 items-center gap-1 rounded-lg px-3 text-sm font-medium text-accent-fg hover:bg-surface"
          aria-label={`Review ${item.label}${item.accountName ? ` for ${item.accountName}` : ''}`}
        >
          Review
          <ArrowRight className="h-3.5 w-3.5" aria-hidden />
        </Link>
      </div>
    </div>
  );
}

const COMPLIANCE_ROWS = 3;
const DAY_MS = 86_400_000;

/** Whole days, negative once the date has passed. Rounded, as the Calendar rounds. */
function daysUntil(value: string): number {
  return Math.round((new Date(value).getTime() - Date.now()) / DAY_MS);
}

function Row({
  label,
  value,
  emphasis,
  tone,
}: {
  label: string;
  value: string;
  emphasis?: boolean;
  tone?: 'positive' | 'danger';
}) {
  return (
    <div className="flex items-baseline justify-between gap-3">
      <span className={emphasis ? 'font-medium' : 'text-muted'}>{label}</span>
      <span
        className={[
          'nums tabular-nums',
          emphasis ? 'text-base font-semibold' : '',
          tone === 'positive' ? 'text-positive' : tone === 'danger' ? 'text-danger' : '',
        ].join(' ')}
      >
        {value}
      </span>
    </div>
  );
}

function greeting(): string {
  const hour = new Date().getHours();
  if (hour < 12) return 'Good morning';
  if (hour < 18) return 'Good afternoon';
  return 'Good evening';
}
