"use client";

import * as React from "react";
import { CircleCheckIcon, CircleMinusIcon, CircleXIcon, PlugZapIcon } from "lucide-react";

import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/lib/utils";

import { AsyncBadge } from "./async-status";
import { TONE_BOX } from "./tones";

const n2 = (n: number) => n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });

/* ---------------------------------- 28. Promotions ---------------------------------- */

export type Promo = {
  name: string;
  kind: "Percent" | "Fixed";
  value: number;
  scope: "Basket" | "Category" | "Product";
  target?: string;
  minBasket: number;
  start: string;
  end: string;
  branches: string[];
  group?: string;
  active: boolean;
};

export type PromoStatus = "Off" | "Scheduled" | "Running" | "Ended";

export function promoStatus(p: Promo, today: string): PromoStatus {
  if (!p.active) return "Off";
  if (today < p.start) return "Scheduled";
  if (today > p.end) return "Ended";
  return "Running";
}

/** Problems in words; a promotion that can't apply is refused rather than saved silently. */
export function promoProblems(p: Promo): string[] {
  const out: string[] = [];
  if (!p.name.trim()) out.push("Give the promotion a name customers and staff will recognise.");
  if (!(p.value > 0)) out.push("Enter the discount.");
  if (p.kind === "Percent" && p.value > 100) out.push("A percentage discount can't be more than 100%.");
  if (p.kind === "Fixed" && p.minBasket > 0 && p.value >= p.minBasket) out.push(`A KES ${n2(p.value)} discount on a KES ${n2(p.minBasket)} minimum basket gives the goods away. Raise the minimum or lower the discount.`);
  if (p.scope !== "Basket" && !p.target) out.push(`Choose the ${p.scope.toLowerCase()} it applies to.`);
  if (!p.start || !p.end) out.push("Set the start and end dates.");
  else if (p.end < p.start) out.push("The end date is before the start date.");
  if (!p.branches.length) out.push("Choose at least one branch.");
  return out;
}

/** What the promotion takes off a given basket, so the person setting it up sees the effect in shillings. */
export function promoDiscount(p: Promo, basket: number, eligible: number) {
  if (basket < p.minBasket) return 0;
  const base = p.scope === "Basket" ? basket : eligible;
  const d = p.kind === "Percent" ? (base * p.value) / 100 : Math.min(p.value, base);
  return Math.round(d * 100) / 100;
}

export function PromoBadge({ status }: { status: PromoStatus }) {
  const tone = status === "Running" ? TONE_BOX.success : status === "Scheduled" ? TONE_BOX.info : TONE_BOX.neutral;
  return <span className={cn("inline-flex rounded-full border px-2 py-0.5 text-xs font-medium", tone)}>{status}</span>;
}

/* ---------------------------------- 30. Alert rules ---------------------------------- */

export type AlertRule = { id: string; name: string; when: string; threshold: number; unit: string; roles: string[]; channels: { app: boolean; sms: boolean; email: boolean }; on: boolean };

/**
 * Alert rules (PRD 9.9, ST-14): what fires, at what threshold, to which roles, on which channels. SMS and
 * email are offered only once their integration is connected; otherwise they say why they're unavailable.
 */
export function AlertRules({ rules, roles, connected, onChange }: { rules: AlertRule[]; roles: string[]; connected: { sms: boolean; email: boolean }; onChange: (id: string, patch: Partial<AlertRule>) => void }) {
  return (
    <div className="overflow-x-auto rounded-lg border border-[var(--color-border-panel)] bg-card">
      <table className="w-full border-collapse text-sm">
        <caption className="sr-only">Alert rules</caption>
        <thead className="bg-[var(--color-bg-surface-subtle)] text-left text-xs text-muted-foreground">
          <tr>
            {["On", "Alert", "Threshold", "Who gets it", "In app", "SMS", "Email"].map((h) => (
              <th key={h} scope="col" className={cn("px-3 py-2 font-medium", ["On", "In app", "SMS", "Email"].includes(h) && "text-center")}>
                {h}
              </th>
            ))}
          </tr>
        </thead>
        <tbody className="divide-y divide-[var(--color-border-subtle)]">
          {rules.map((r) => (
            <tr key={r.id} className={cn(!r.on && "text-muted-foreground")}>
              <td className="px-3 py-2 text-center">
                <Checkbox aria-label={`${r.name} on`} checked={r.on} onCheckedChange={(v) => onChange(r.id, { on: !!v })} />
              </td>
              <th scope="row" className="px-3 py-2 text-left font-medium">
                {r.name}
                <span className="block text-xs font-normal text-muted-foreground">{r.when}</span>
              </th>
              <td className="px-3 py-2">
                <span className="inline-flex items-center gap-1.5">
                  <Input className="h-8 w-20 tabular-nums" inputMode="numeric" aria-label={`${r.name} threshold`} value={String(r.threshold)} disabled={!r.on} onChange={(e) => onChange(r.id, { threshold: Number(e.target.value.replace(/\D/g, "")) || 0 })} />
                  <span className="text-xs text-muted-foreground">{r.unit}</span>
                </span>
              </td>
              <td className="px-3 py-2">
                <span className="flex flex-wrap gap-x-3 gap-y-1">
                  {roles.map((role) => (
                    <label key={role} className="inline-flex items-center gap-1 text-xs">
                      <Checkbox aria-label={`${r.name} to ${role}`} checked={r.roles.includes(role)} disabled={!r.on} onCheckedChange={(v) => onChange(r.id, { roles: v ? [...r.roles, role] : r.roles.filter((x) => x !== role) })} />
                      {role}
                    </label>
                  ))}
                </span>
                {r.on && !r.roles.length ? <span className="text-xs font-medium text-status-warning-text">Nobody gets this alert</span> : null}
              </td>
              {(["app", "sms", "email"] as const).map((ch) => {
                const unavailable = ch !== "app" && !connected[ch];
                return (
                  <td key={ch} className="px-3 py-2 text-center">
                    <Checkbox
                      aria-label={`${r.name} by ${ch === "app" ? "in-app notification" : ch.toUpperCase()}${unavailable ? ", not connected" : ""}`}
                      checked={r.channels[ch] && !unavailable}
                      disabled={!r.on || unavailable}
                      onCheckedChange={(v) => onChange(r.id, { channels: { ...r.channels, [ch]: !!v } })}
                    />
                    {unavailable ? <span className="block text-[11px] text-muted-foreground">Not connected</span> : null}
                  </td>
                );
              })}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

/* ------------------------------- 31. Integration status ------------------------------- */

/** `ignored`: received but deliberately not acted on (an unmatched or duplicate callback, IG-04). Not a failure. */
export type IntegrationEvent = { id: string; at: string; event: string; ref: string; ok: boolean; ignored?: boolean; detail?: string };

/** Header of an integration page (PRD 10.x, IG-01): status in words, environment, last success. */
export function IntegrationHeader({ name, status, environment, lastOk }: { name: string; status: "Connected" | "Not connected" | "Failing"; environment: "Sandbox" | "Live"; lastOk?: string }) {
  return (
    <div className="flex flex-wrap items-center gap-2">
      <PlugZapIcon className="size-5 text-muted-foreground" aria-hidden />
      <span className="text-base font-semibold">{name}</span>
      <AsyncBadge state={status === "Connected" ? "done" : status === "Failing" ? "failed" : "queued"} label={status} />
      <span className={cn("rounded-full border px-2 py-0.5 text-xs font-medium", environment === "Live" ? TONE_BOX.info : TONE_BOX.warning)}>{environment}</span>
      {lastOk ? <span className="text-xs text-muted-foreground">Last success {lastOk}</span> : null}
    </div>
  );
}

/** Event log: every request and callback, success or failure with the reason in words; never the secrets. */
export function IntegrationLog({ events }: { events: IntegrationEvent[] }) {
  return (
    <div className="overflow-x-auto rounded-lg border border-[var(--color-border-panel)] bg-card">
      <table className="w-full border-collapse text-sm">
        <caption className="sr-only">Integration log</caption>
        <thead className="bg-[var(--color-bg-surface-subtle)] text-left text-xs text-muted-foreground">
          <tr>
            {["When", "Event", "Reference", "Result"].map((h) => (
              <th key={h} scope="col" className="px-3 py-2 font-medium">
                {h}
              </th>
            ))}
          </tr>
        </thead>
        <tbody className="divide-y divide-[var(--color-border-subtle)]">
          {events.map((e) => (
            <tr key={e.id}>
              <td className="px-3 py-2 whitespace-nowrap text-muted-foreground tabular-nums">{e.at}</td>
              <td className="px-3 py-2">{e.event}</td>
              <td className="px-3 py-2 font-mono text-xs">{e.ref}</td>
              <td className="px-3 py-2">
                <span className={cn("inline-flex items-center gap-1 text-xs font-medium", e.ignored ? "text-status-warning-text" : e.ok ? "text-status-success-text" : "text-status-error-text")}>
                  {e.ignored ? <CircleMinusIcon className="size-3.5" aria-hidden /> : e.ok ? <CircleCheckIcon className="size-3.5" aria-hidden /> : <CircleXIcon className="size-3.5" aria-hidden />}
                  {e.ignored ? "Ignored" : e.ok ? "OK" : "Failed"}
                </span>
                {e.detail ? <span className="block text-xs text-muted-foreground">{e.detail}</span> : null}
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

/* ------------------------------ 32. eTIMS item registration ------------------------------ */

export type EtimsItem = { id: number; name: string; itemClass: string; itemType: "Finished product" | "Service" | "Raw material"; origin: string; packaging: string; quantityUnit: string; taxCode: string; status: "Registered" | "Not registered" | "Rejected"; code?: string; reason?: string };

/** What's missing before an item can be sent to KRA, in words. */
export function etimsMissing(i: EtimsItem) {
  const m: string[] = [];
  if (!i.itemClass) m.push("item class");
  if (!i.origin) m.push("country of origin");
  if (!i.packaging) m.push("packaging unit");
  if (!i.quantityUnit) m.push("quantity unit");
  return m;
}

/* ---------------------------------- 33. Templates ---------------------------------- */

/** GSM-7 basic characters; anything else (curly quotes, emoji, ñ…) makes the SMS Unicode, with fewer characters per part. */
const GSM = /^[@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞ ÆæßÉ !"#¤%&'()*+,\-./0-9:;<=>?¡A-ZÄÖÑÜ§¿a-zäöñüà^{}\\[~\]|€]*$/;

export function smsParts(text: string) {
  const unicode = !GSM.test(text);
  const single = unicode ? 70 : 160;
  const multi = unicode ? 67 : 153;
  const len = [...text].length;
  const parts = len <= single ? 1 : Math.ceil(len / multi);
  return { len, unicode, parts, perPart: parts === 1 ? single : multi };
}

/** Placeholders like {customer_name}; unknown ones are reported so a message never goes out with braces in it. */
export function templateCheck(text: string, allowed: string[]) {
  const used = [...text.matchAll(/\{([a-z_]+)\}/g)].map((m) => m[1]);
  const unknown = [...new Set(used.filter((u) => !allowed.includes(u)))];
  const unclosed = /\{[^}]*$|\{[^a-z_}]/.test(text);
  return { used, unknown, unclosed };
}

export const fillTemplate = (text: string, sample: Record<string, string>) => text.replace(/\{([a-z_]+)\}/g, (all, k: string) => sample[k] ?? all);

/**
 * Message template editor (PRD 10.4, 10.5): placeholders are inserted from a list, not typed from memory;
 * unknown ones are errors. SMS shows length, parts and whether a character forced Unicode (which cuts each
 * part to 70 characters), with the preview filled with sample data.
 */
export function TemplateEditor({ value, onChange, placeholders, sample, channel, costPerPart }: { value: string; onChange: (v: string) => void; placeholders: { key: string; label: string }[]; sample: Record<string, string>; channel: "SMS" | "Email"; costPerPart?: number }) {
  const ref = React.useRef<HTMLTextAreaElement>(null);
  const check = templateCheck(value, placeholders.map((p) => p.key));
  const preview = fillTemplate(value, sample);
  const sms = smsParts(preview);
  const insert = (k: string) => {
    const el = ref.current;
    const at = el?.selectionStart ?? value.length;
    const next = `${value.slice(0, at)}{${k}}${value.slice(el?.selectionEnd ?? at)}`;
    onChange(next);
    setTimeout(() => {
      el?.focus();
      el?.setSelectionRange(at + k.length + 2, at + k.length + 2);
    }, 0);
  };
  return (
    <div className="grid gap-4 lg:grid-cols-2">
      <div className="space-y-2">
        <div className="flex flex-wrap gap-1.5" role="group" aria-label="Insert a placeholder">
          {placeholders.map((p) => (
            <button key={p.key} type="button" onClick={() => insert(p.key)} className="cursor-pointer rounded-sm border border-[var(--color-border-panel)] bg-[var(--color-bg-surface-subtle)] px-1.5 py-0.5 font-mono text-xs hover:border-[var(--color-border-strong)]" title={p.label}>
              {`{${p.key}}`}
            </button>
          ))}
        </div>
        <Textarea ref={ref} rows={channel === "SMS" ? 4 : 8} value={value} onChange={(e) => onChange(e.target.value)} aria-label={`${channel} template`} aria-invalid={check.unknown.length > 0 || check.unclosed} />
        {check.unknown.length ? <p className="text-xs text-status-error-text">Unknown {check.unknown.length === 1 ? "placeholder" : "placeholders"}: {check.unknown.map((u) => `{${u}}`).join(", ")}. Use the buttons above.</p> : null}
        {check.unclosed ? <p className="text-xs text-status-error-text">A placeholder isn&apos;t closed with {"}"}.</p> : null}
      </div>
      <div className="space-y-2">
        <p className="text-xs font-medium text-muted-foreground">Preview with sample data</p>
        <div className={cn("rounded-lg border border-[var(--color-border-panel)] bg-card p-3 text-sm whitespace-pre-wrap", channel === "SMS" && "max-w-xs rounded-2xl bg-muted")}>{preview || <span className="text-muted-foreground">Empty</span>}</div>
        {channel === "SMS" ? (
          <p className={cn("text-xs tabular-nums", sms.parts > 1 ? "text-status-warning-text" : "text-muted-foreground")}>
            {sms.len} characters · {sms.parts} {sms.parts === 1 ? "part" : "parts"} of up to {sms.perPart}
            {sms.unicode ? " · Unicode (a special character cuts each part to 70)" : ""}
            {costPerPart ? ` · about KES ${n2(sms.parts * costPerPart)} per message` : ""}
          </p>
        ) : null}
      </div>
    </div>
  );
}

/* ---------------------------------- 33b. Send log ---------------------------------- */

export type SentMessage = { id: string; at: string; channel: "SMS" | "Email"; to: string; template: string; status: "Delivered" | "Sent" | "Failed" | "Bounced"; parts?: number; cost?: number; reason?: string };

/**
 * Sent messages (PRD 10.4, 10.5): every SMS and email with its delivery status from the provider. Failures
 * and bounces say why in words and can be resent once the cause is fixed. Recipients are partly masked.
 */
export function SendLog({ messages, onResend }: { messages: SentMessage[]; onResend?: (id: string) => void }) {
  const [filter, setFilter] = React.useState<"all" | "problems">("all");
  const shown = filter === "problems" ? messages.filter((m) => m.status === "Failed" || m.status === "Bounced") : messages;
  const cost = messages.reduce((s, m) => s + (m.cost ?? 0), 0);
  const problems = messages.filter((m) => m.status === "Failed" || m.status === "Bounced").length;
  return (
    <div className="space-y-2">
      <div className="flex flex-wrap items-center gap-3 text-sm">
        <label className="flex items-center gap-2">
          <Checkbox checked={filter === "problems"} onCheckedChange={(v) => setFilter(v ? "problems" : "all")} /> Failed and bounced only ({problems})
        </label>
        <span className="ml-auto text-xs text-muted-foreground tabular-nums">SMS cost today KES {n2(cost)}</span>
      </div>
      <div className="overflow-x-auto rounded-lg border border-[var(--color-border-panel)] bg-card">
        <table className="w-full border-collapse text-sm">
          <caption className="sr-only">Sent messages</caption>
          <thead className="bg-[var(--color-bg-surface-subtle)] text-left text-xs text-muted-foreground">
            <tr>
              {["When", "To", "Message", "Status", ""].map((h, i) => (
                <th key={i} scope="col" className="px-3 py-2 font-medium">
                  {h || <span className="sr-only">Actions</span>}
                </th>
              ))}
            </tr>
          </thead>
          <tbody className="divide-y divide-[var(--color-border-subtle)]">
            {shown.map((m) => {
              const bad = m.status === "Failed" || m.status === "Bounced";
              return (
                <tr key={m.id}>
                  <td className="px-3 py-2 whitespace-nowrap text-muted-foreground tabular-nums">{m.at}</td>
                  <td className="px-3 py-2">
                    <span className="text-xs text-muted-foreground">{m.channel}</span>
                    <span className="block font-mono text-xs">{m.to}</span>
                  </td>
                  <td className="px-3 py-2">
                    {m.template}
                    {m.parts ? <span className="block text-xs text-muted-foreground tabular-nums">{m.parts} {m.parts === 1 ? "part" : "parts"} · KES {n2(m.cost ?? 0)}</span> : null}
                  </td>
                  <td className="px-3 py-2">
                    <AsyncBadge state={bad ? "failed" : m.status === "Delivered" ? "done" : "waiting"} label={m.status} className={m.status === "Sent" ? "[&>svg]:animate-none" : undefined} />
                    {m.reason ? <span className="mt-0.5 block text-xs text-muted-foreground">{m.reason}</span> : null}
                  </td>
                  <td className="px-2 py-2 text-right">
                    {bad && onResend ? (
                      <button type="button" className="cursor-pointer text-xs font-medium text-primary underline-offset-2 hover:underline" onClick={() => onResend(m.id)}>
                        Resend
                      </button>
                    ) : null}
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
}

/* --------------------------- 31b. Provider-specific checks --------------------------- */

export type ProviderCheck = { label: string; ok: boolean; detail: string };

/**
 * The checks that decide whether an integration works, each with its result in words: an eTIMS device's
 * initialisation, an SMS sender ID's approval and balance, an email domain's SPF and DKIM, a bank feed's
 * last import. Failing checks say what to do.
 */
export function ProviderChecks({ checks }: { checks: ProviderCheck[] }) {
  return (
    <ul className="divide-y divide-[var(--color-border-subtle)] rounded-lg border border-[var(--color-border-panel)] text-sm bg-card">
      {checks.map((c) => (
        <li key={c.label} className="flex items-start gap-2 px-3 py-2">
          {c.ok ? <CircleCheckIcon className="mt-0.5 size-4 shrink-0 text-status-success-text" aria-hidden /> : <CircleXIcon className="mt-0.5 size-4 shrink-0 text-status-error-text" aria-hidden />}
          <span>
            <span className="font-medium">{c.label}</span>
            <span className="sr-only">{c.ok ? ", passing" : ", failing"}</span>
            <span className="block text-xs text-muted-foreground">{c.detail}</span>
          </span>
        </li>
      ))}
    </ul>
  );
}
