"use client";

import { useState } from "react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { licence, type LicenseInfo, type LicenseState } from "@/lib/api";
import { useLicense } from "@/lib/useLicense";
import { Button, Card } from "@/components/ui";
import { Group } from "@/components/settings/Group";
import { inputClass } from "@/components/settings/Field";

/*
 * A licence is a document issued, dated, signed, and carrying a reference
 * you quote to support. This page is built to read as that document rather
 * than as another settings form, which is also how adminsite/ presents the
 * same object from the issuing side: state as a pill that carries a label and
 * a shape, the reference on a mono record line, and the entitlement as keyed
 * fields.
 *
 * Counted limits are drawn as meters because that is already how the console
 * shows headroom on a server's disks. Licence headroom and disk headroom are
 * the same question, so they read the same way.
 */

const UNLIMITED = -1;

// Written as whole class names, never composed, so Tailwind's scanner finds them.
const STATE: Record<LicenseState, { label: string; rule: string; text: string }> = {
    valid: { label: "Valid", rule: "border-l-success", text: "text-success" },
    expired: { label: "Expired", rule: "border-l-warning", text: "text-warning" },
    invalid: { label: "Invalid", rule: "border-l-danger", text: "text-danger" },
};

// The API returns stable identifiers rather than sentences, so the wording is
// this app's to choose. Each one says what is wrong and what to do about it.
const REASON: Record<string, string> = {
    no_license: "No licence is installed on this instance. Paste one below to activate it.",
    bad_signature: "This licence's signature could not be verified. It may have been edited or truncated in transit paste the original again.",
    deployment_mismatch: "This licence was issued for a different kind of deployment, so it cannot be used here.",
    instance_mismatch: "This licence was issued for a different instance. Check the instance ID below against the one you bought against.",
    expired: "This licence has passed its expiry date. Your servers and monitors keep running, but changes are disabled until it is renewed.",
};

const SOURCE: Record<string, string> = {
    stored: "Stored on this instance",
    env: "Provided by the VANTAGE_LICENSE environment variable",
    none: "None installed",
};

/*
 * The three stable identifiers, in prose. An unknown or empty value renders
 * NOTHING rather than a fallback: a licence signed before support_level existed
 * has no support level, and inventing one would promise something we have not
 * sold.
 */
const SUPPORT_LABELS: Record<string, string> = {
    community: "Community support",
    email_24_5: "Email support, 24/5",
    email_call_24_7: "Email and phone support, 24/7",
};

/*
 * Retention is a duration, not a cap, so it gets a fact rather than a meter.
 * Allowance's bar needs a numerator and there isn't one how much of a
 * retention window have you "used"?
 */
function Retention({ days }: { days: number }) {
    const forever = days === UNLIMITED;
    return (
        <Card padding={false} className="p-4">
            <p className="font-mono text-[0.62rem] uppercase tracking-[0.14em] text-text-tertiary">Audit history</p>
            <p className="mt-2 text-2xl font-extrabold tracking-[-0.03em] tabular-nums text-text-primary">
                {forever ? "Kept" : days}
                <span className="ml-1.5 text-sm font-medium tracking-normal text-text-secondary">{forever ? "indefinitely" : "days"}</span>
            </p>
            <p className="mt-3 font-mono text-[0.62rem] uppercase tracking-[0.14em] text-text-secondary">{forever ? "Never trimmed" : "Older entries removed daily"}</p>
        </Card>
    );
}

/** A keyed field on the record, the way a certificate prints them. */
function Keyed({ label, children }: { label: string; children: React.ReactNode }) {
    return (
        <div className="min-w-0">
            <p className="font-mono text-[0.62rem] uppercase tracking-[0.14em] text-text-tertiary">{label}</p>
            <div className="mt-1 text-sm text-text-primary">{children}</div>
        </div>
    );
}

function Allowance({ label, used, limit }: { label: string; used: number; limit: number }) {
    const unlimited = limit === UNLIMITED;
    // Guard the divide: a zero limit is a real answer from the API, not a bug.
    const pct = unlimited || limit <= 0 ? 0 : Math.min(100, Math.round((used / limit) * 100));
    const fill = pct >= 100 ? "bg-danger" : pct >= 80 ? "bg-warning" : "bg-accent";

    // padding={false} because Card's own p-6 is emitted after p-4 and would win.
    return (
        <Card padding={false} className="p-4">
            <p className="font-mono text-[0.62rem] uppercase tracking-[0.14em] text-text-tertiary">{label}</p>
            <p className="mt-2 text-2xl font-extrabold tracking-[-0.03em] tabular-nums text-text-primary">
                {used}
                <span className="ml-1.5 text-sm font-medium tracking-normal text-text-secondary">{unlimited ? "used" : `of ${limit}`}</span>
            </p>
            {unlimited ? (
                <p className="mt-3 font-mono text-[0.62rem] uppercase tracking-[0.14em] text-text-secondary">No limit</p>
            ) : (
                <div className="mt-3 h-1.5 overflow-hidden rounded-full bg-surface-2" role="meter" aria-valuenow={used} aria-valuemin={0} aria-valuemax={limit} aria-label={`${label}: ${used} of ${limit} used`}>
                    <div className={`h-full rounded-full ${fill}`} style={{ width: `${pct}%` }} />
                </div>
            )}
        </Card>
    );
}

function Feature({ label, included }: { label: string; included: boolean }) {
    return (
        <div className="flex items-center gap-3 py-2.5">
            <span aria-hidden className={`flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-sm border text-[0.6rem] font-bold ${included ? "border-success text-success" : "border-border text-text-tertiary"}`}>
                {included ? "✓" : "–"}
            </span>
            <span className="text-sm text-text-primary">{label}</span>
            <span className={`ml-auto font-mono text-[0.62rem] uppercase tracking-[0.14em] ${included ? "text-success" : "text-text-tertiary"}`}>{included ? "Included" : "Not included"}</span>
        </div>
    );
}

function RecordPanel({ license }: { license: LicenseInfo }) {
    const [copied, setCopied] = useState(false);
    const s = STATE[license.state];
    const days = license.days_remaining;

    async function copyId() {
        await navigator.clipboard.writeText(license.instance_id);
        setCopied(true);
        setTimeout(() => setCopied(false), 2000);
    }

    return (
        <Card className={`border-l-2 ${s.rule}`}>
            <div className="flex flex-wrap items-baseline gap-x-4 gap-y-2">
                {/* No status dot: the word is the label, and the card's left rule
                    already carries the colour. A dot would be the third telling. */}
                <h2 className={`text-2xl font-extrabold tracking-[-0.03em] ${s.text}`}>{s.label}</h2>
                {license.tier && <p className="text-sm text-text-secondary">{license.tier} tier</p>}
                {license.support_level && SUPPORT_LABELS[license.support_level] && <p className="text-sm text-text-secondary">{SUPPORT_LABELS[license.support_level]}</p>}
            </div>

            {license.reason && <p className="mt-3 max-w-prose text-sm text-text-secondary">{REASON[license.reason] ?? license.reason}</p>}

            <div className="mt-5 grid gap-5 border-t border-border-soft pt-5 sm:grid-cols-2 lg:grid-cols-3">
                <Keyed label="Expires">
                    {license.expires_at ? (
                        <>
                            {new Date(license.expires_at).toLocaleDateString(undefined, { day: "numeric", month: "long", year: "numeric" })}
                            {typeof days === "number" && days >= 0 && (
                                <span className="ml-1.5 text-text-secondary">
                                    ({days} day{days === 1 ? "" : "s"})
                                </span>
                            )}
                        </>
                    ) : (
                        <span className="text-text-secondary">No expiry</span>
                    )}
                </Keyed>

                <Keyed label="Source">
                    <span className="text-text-secondary">{SOURCE[license.source] ?? license.source}</span>
                </Keyed>

                <div className="min-w-0 sm:col-span-2 lg:col-span-1">
                    <Keyed label="Instance ID">
                        <div className="flex items-center gap-2">
                            <code className="truncate font-mono text-xs text-text-primary">{license.instance_id}</code>
                            <button type="button" onClick={copyId} className="flex-shrink-0 rounded-sm border border-border px-1.5 py-0.5 font-mono text-[0.6rem] uppercase tracking-[0.1em] text-text-secondary transition-colors hover:border-text-tertiary hover:text-text-primary">
                                {copied ? "Copied" : "Copy"}
                            </button>
                        </div>
                    </Keyed>
                    <p className="mt-1.5 text-xs text-text-tertiary">Quote this when buying or activating a licence.</p>
                </div>
            </div>
        </Card>
    );
}

export default function LicensePage() {
    const { license, isLoading } = useLicense();
    const queryClient = useQueryClient();
    const [blob, setBlob] = useState("");
    const [fileName, setFileName] = useState("");
    const [error, setError] = useState("");

    const save = useMutation({
        mutationFn: () => licence.put(blob.trim()),
        onSuccess: () => {
            setBlob("");
            setFileName("");
            setError("");
            queryClient.invalidateQueries({ queryKey: ["license"] });
        },
        onError: (e: Error) => setError(e.message),
    });

    if (isLoading) {
        return (
            <div className="flex h-full items-center justify-center">
                <div className="h-8 w-8 animate-spin rounded-full border-2 border-border border-t-accent" />
            </div>
        );
    }

    if (!license) {
        return (
            <div className="p-4 sm:p-6 lg:p-8">
                <Card className="max-w-lg">
                    <h1 className="text-base font-bold text-text-primary">Licence unavailable</h1>
                    <p className="mt-1 text-sm text-text-secondary">The licence state could not be read. Reload the page, and check the server logs if it keeps failing.</p>
                </Card>
            </div>
        );
    }

    // A cloud instance's licence is injected by admin, so there is nothing for
    // a customer to paste. The API refuses the POST either way; this is what
    // stops the page offering an action that cannot succeed.
    const isCloud = license.deployment === "cloud";
    const hqUrl = process.env.NEXT_PUBLIC_HQ_URL ?? "";

    return (
        <div className="p-4 sm:p-6 lg:p-8">
            <div className="mx-auto max-w-5xl space-y-10">
                <div>
                    <h1 className="text-2xl font-extrabold tracking-[-0.03em] text-text-primary">Licence</h1>
                    <p className="mt-1 text-sm text-text-secondary">What this instance is entitled to, and how much of it is in use.</p>
                </div>

                <RecordPanel license={license} />

                <Group label="Allowances">
                    <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
                        <Allowance label="Servers" used={license.usage.servers} limit={license.limits.max_servers} />
                        <Allowance label="Monitors" used={license.usage.monitors} limit={license.limits.max_monitors} />
                        <Allowance label="Secret groups" used={license.usage.secret_groups} limit={license.limits.max_secret_groups} />
                        <Allowance label="Notification channels" used={license.usage.channels} limit={license.limits.max_channels} />
                        <Retention days={license.limits.audit_retention_days} />
                    </div>

                    <Card padding={false} className="px-5 py-2">
                        <div className="divide-y divide-border-soft">
                            <Feature label="Browser console" included={Boolean(license.features.console)} />
                            <Feature label="Single sign-on" included={Boolean(license.features.oidc)} />
                            <Feature label="Vulnerability Scanning" included={Boolean(license.features.vuln_scanning)} />
                        </div>
                    </Card>
                </Group>

                {isCloud ? (
                    <Group label="Where this licence comes from">
                        <Card>
                            <p className="max-w-prose text-sm text-text-secondary">This is a cloud instance, so its licence is issued and renewed in Vantage HQ and applied here automatically. There is nothing to paste.</p>
                            {hqUrl && (
                                <div className="mt-5 border-t border-border-soft pt-5">
                                    <a href={hqUrl} target="_blank" rel="noreferrer">
                                        <Button type="button" variant="secondary">
                                            Open Vantage HQ
                                        </Button>
                                    </a>
                                </div>
                            )}
                        </Card>
                    </Group>
                ) : (
                    <Group label="Add or replace">
                        <Card>
                            <label htmlFor="licence-blob" className="mb-1.5 block text-sm font-medium text-text-secondary">
                                Licence key
                            </label>
                            <textarea
                                id="licence-blob"
                                className={`${inputClass} h-32 resize-y font-mono text-xs`}
                                placeholder="Paste your licence key"
                                value={blob}
                                onChange={(e) => {
                                    setBlob(e.target.value);
                                    setFileName("");
                                }}
                            />

                            <div className="mt-3 flex flex-wrap items-center gap-3">
                                {/* A bare file input cannot be styled, so the button is the
                                label and the input itself is visually hidden. */}
                                <label className="inline-flex cursor-pointer items-center gap-2 rounded border border-border bg-surface px-3 py-1.5 text-sm font-semibold text-text-primary transition-colors hover:border-text-tertiary focus-within:ring-2 focus-within:ring-accent">
                                    <input
                                        type="file"
                                        accept=".lic,.txt"
                                        className="sr-only"
                                        onChange={async (e) => {
                                            const f = e.target.files?.[0];
                                            if (!f) return;
                                            setBlob((await f.text()).trim());
                                            setFileName(f.name);
                                        }}
                                    />
                                    Choose a file
                                </label>
                                <span className="text-xs text-text-tertiary">{fileName ? `Loaded ${fileName}` : "or paste the key above"}</span>
                            </div>

                            {error && <p className="mt-4 rounded border border-danger/30 bg-danger/10 px-3 py-2 text-sm text-danger">{error}</p>}

                            <div className="mt-5 border-t border-border-soft pt-5">
                                <Button type="button" variant="primary" loading={save.isPending} disabled={!blob.trim()} onClick={() => save.mutate()}>
                                    {save.isPending ? "Checking…" : "Save licence"}
                                </Button>
                            </div>
                        </Card>
                    </Group>
                )}
            </div>
        </div>
    );
}
