"use client";

import { useState } from "react";
import Link from "next/link";
import { Button } from "@/components/ui";
import type { VulnFinding } from "@/lib/api";
import type { PackageGroup, PackageServer } from "@/lib/vulnPackages";
import { SeverityBadge, StateBadge, relativeTime } from "./SeverityVisuals";

/*
 * One row per package, expandable to the servers carrying it and the CVEs on
 * each.
 *
 * The grouping is the point, and it is the same argument as the CVE grouping it
 * replaced, one level in: an operator upgrades a package, not a CVE. Two CVEs
 * on one apache2 are one upgrade to the higher of the two fix versions, and
 * showing them as two rows with two different targets is how a fleet gets
 * patched to the lower one.
 */

interface Props {
    group: PackageGroup;
    serverName: (serverId: string) => string;
    canAct: boolean;
    onAccept: (f: VulnFinding) => void;
    onUnaccept: (f: VulnFinding) => void;
    onApplyUpdates: (serverId: string) => void;
    applying?: string;
}

export function PackageRow({ group, serverName, canAct, onAccept, onUnaccept, onApplyUpdates, applying }: Props) {
    const [open, setOpen] = useState(false);

    // A package with no fix anywhere cannot be patched, only accepted. Saying so
    // on the collapsed row saves opening it to find there is nothing to do.
    const anyFix = group.servers.some((s) => s.target);

    return (
        // A row inside the page's one bordered container, not a card of its
        // own — the same stack idiom as the monitors and workflows lists.
        <div className="border-t border-border-soft first:border-t-0">
            <button
                onClick={() => setOpen((v) => !v)}
                className="flex w-full items-center gap-3 px-4 py-3.5 text-left transition-colors hover:bg-surface-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-accent sm:px-5"
                aria-expanded={open}
            >
                <span className="font-mono text-xs text-text-tertiary">{open ? "▾" : "▸"}</span>
                <SeverityBadge severity={group.severity} />
                <span className="font-mono text-sm font-medium text-text-primary">{group.package_name}</span>
                <span className="whitespace-nowrap font-mono text-[11px] text-text-tertiary">
                    {group.cve_count} {group.cve_count === 1 ? "CVE" : "CVEs"}
                </span>
                <span className="ml-auto whitespace-nowrap font-mono text-[11px] text-text-tertiary">
                    {group.server_count} {group.server_count === 1 ? "server" : "servers"}
                </span>
                {!anyFix && (
                    <span className="hidden whitespace-nowrap rounded-sm border border-border px-1.5 font-mono text-[10px] uppercase tracking-[0.1em] text-text-tertiary sm:block">
                        no fix
                    </span>
                )}
            </button>

            {open && (
                <div className="border-t border-border-soft bg-surface-2/40">
                    {group.servers.map((s) => (
                        <ServerBlock
                            key={`${s.package_name}:${s.server_id}`}
                            row={s}
                            serverName={serverName}
                            canAct={canAct}
                            onAccept={onAccept}
                            onUnaccept={onUnaccept}
                            onApplyUpdates={onApplyUpdates}
                            applying={applying}
                        />
                    ))}
                </div>
            )}
        </div>
    );
}

function ServerBlock({
    row,
    serverName,
    canAct,
    onAccept,
    onUnaccept,
    onApplyUpdates,
    applying,
}: Omit<Props, "group"> & { row: PackageServer }) {
    const patchable = row.target && row.findings.some((f) => f.state !== "fixed");

    return (
        <div className="border-b border-border-soft px-4 py-3 last:border-b-0 sm:px-5">
            <div className="flex flex-wrap items-center gap-x-4 gap-y-2">
                <Link href={`/servers/${row.server_id}`} className="text-sm text-accent hover:underline">
                    {serverName(row.server_id)}
                </Link>

                <span className="font-mono text-xs text-text-secondary">{row.installed_version}</span>

                {/* One target, the highest across every CVE on this package.
                    A lower fix version does not remediate a higher one, so it
                    is never the number offered. */}
                <span className="font-mono text-xs text-text-tertiary">{row.target ? `→ ${row.target}` : "no fix published"}</span>

                {row.superseded && (
                    <span
                        className="whitespace-nowrap rounded-sm border border-border px-1.5 font-mono text-[10px] uppercase tracking-[0.1em] text-text-tertiary"
                        title="Several CVEs name different fix versions; the highest is shown and covers the rest."
                    >
                        supersedes lower fixes
                    </span>
                )}

                {canAct && patchable && (
                    <div className="ml-auto">
                        {/* Remediation is the existing endpoint, not a new
                            mechanism: see it, patch it, one place. */}
                        <Button size="sm" variant="secondary" loading={applying === row.server_id} onClick={() => onApplyUpdates(row.server_id)}>
                            Apply updates
                        </Button>
                    </div>
                )}
            </div>

            <div className="mt-2 space-y-1.5">
                {row.findings.map((f) => (
                    <div key={f.id} className="flex flex-wrap items-center gap-x-3 gap-y-1.5 pl-1">
                        <SeverityBadge severity={f.severity} />
                        <span className="font-mono text-xs text-text-secondary">{f.cve_id}</span>
                        {f.title && <span className="hidden truncate text-xs text-text-tertiary sm:block">{f.title}</span>}

                        {/* The per-CVE fix stays visible when it differs from
                            the row's target, so the rollup can be checked
                            rather than taken on trust. */}
                        {f.fixed_in && f.fixed_in !== row.target && (
                            <span className="font-mono text-[11px] text-text-tertiary">fixed in {f.fixed_in}</span>
                        )}
                        {!f.fixed_in && <span className="font-mono text-[11px] text-text-tertiary">no fix</span>}

                        <StateBadge state={f.state} />

                        {f.state === "accepted" && f.accepted && (
                            <span className="text-xs text-text-tertiary">
                                {f.accepted.reason} · reopens {new Date(f.accepted.until).toLocaleDateString()}
                            </span>
                        )}
                        {f.state !== "accepted" && <span className="text-xs text-text-tertiary">first seen {relativeTime(f.first_seen)}</span>}

                        {canAct && (
                            <div className="ml-auto">
                                {f.state === "accepted" ? (
                                    <Button size="sm" variant="ghost" onClick={() => onUnaccept(f)}>
                                        Reopen
                                    </Button>
                                ) : (
                                    f.state === "open" && (
                                        <Button size="sm" variant="ghost" onClick={() => onAccept(f)}>
                                            Accept
                                        </Button>
                                    )
                                )}
                            </div>
                        )}
                    </div>
                ))}
            </div>
        </div>
    );
}
