import { useCallback, useEffect, useState } from "react"
import {
  CheckCircle2,
  Globe,
  Network,
  Plus,
  RefreshCw,
  Shield,
  Trash2,
  Wifi,
} from "lucide-react"

import { useArcadiaClient } from "@crema/arcadia-core-client"
import { useToast } from "@crema/notification-ui"
import { ConfirmDialog, EmptyState } from "@crema/feedback-ui"

import { AppShell } from "~/components/layout/app-shell"
import { DataState, DialogError } from "~/components/data-state"
import { errorMessage } from "~/lib/errors"
import { Badge } from "~/components/ui/badge"
import { Button } from "~/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "~/components/ui/card"
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "~/components/ui/dialog"
import { Input } from "~/components/ui/input"
import { Label } from "~/components/ui/label"
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "~/components/ui/select"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs"
import {
  assignFloatingIp,
  createDnsRecord,
  deleteDnsRecord,
  deleteFirewall,
  listDnsRecords,
  listDomains,
  listFirewalls,
  listFloatingIps,
  listVpcs,
  unassignFloatingIp,
  type DnsRecord,
  type Domain,
  type Firewall,
  type FloatingIp,
  type Vpc,
} from "~/lib/arcadia/networking"
import { listDroplets, type Droplet } from "~/lib/arcadia/monitoring"
import { pageTitle } from "~/lib/page-meta"
import { useSession } from "~/lib/session"
import { useRegisterContext } from "@crema/aifirst-ui/context"

export const meta = () => pageTitle("Networking")

const DNS_TYPES = ["A", "AAAA", "CNAME", "MX", "TXT", "NS", "SRV", "CAA"]

export default function NetworkingRoute() {
  const session = useSession()
  const arcadia = useArcadiaClient()

  const [firewalls, setFirewalls] = useState<Firewall[]>([])
  const [vpcs, setVpcs] = useState<Vpc[]>([])
  const [domains, setDomains] = useState<Domain[]>([])
  const [floatingIps, setFloatingIps] = useState<FloatingIp[]>([])
  const [droplets, setDroplets] = useState<Droplet[]>([])
  const [loading, setLoading] = useState(true)
  // One error per tab. These endpoints legitimately 503 when DigitalOcean isn't
  // configured on a deployment — which must read as "arcadia hit a server
  // error", never as the flat lie "No firewalls." A single Promise.all also
  // used to mean one 503 wiped all four tabs; allSettled keeps them apart.
  const [firewallsError, setFirewallsError] = useState<unknown>(null)
  const [vpcsError, setVpcsError] = useState<unknown>(null)
  const [domainsError, setDomainsError] = useState<unknown>(null)
  const [floatingIpsError, setFloatingIpsError] = useState<unknown>(null)

  const refresh = useCallback(async () => {
    setLoading(true)
    setFirewallsError(null)
    setVpcsError(null)
    setDomainsError(null)
    setFloatingIpsError(null)

    const [f, v, d, fi, dr] = await Promise.allSettled([
      listFirewalls(arcadia),
      listVpcs(arcadia),
      listDomains(arcadia),
      listFloatingIps(arcadia),
      listDroplets(arcadia),
    ])

    if (f.status === "fulfilled") setFirewalls(f.value)
    else {
      setFirewalls([])
      setFirewallsError(f.reason)
    }

    if (v.status === "fulfilled") setVpcs(v.value)
    else {
      setVpcs([])
      setVpcsError(v.reason)
    }

    if (d.status === "fulfilled") setDomains(d.value)
    else {
      setDomains([])
      setDomainsError(d.reason)
    }

    if (fi.status === "fulfilled") setFloatingIps(fi.value)
    else {
      setFloatingIps([])
      setFloatingIpsError(fi.reason)
    }

    // Droplets only populate the "assign to" picker; the picker already says
    // "No droplets" when it's empty, so a failure needs no error surface here.
    setDroplets(dr.status === "fulfilled" ? dr.value : [])

    setLoading(false)
  }, [arcadia])

  useEffect(() => {
    if (session) refresh()
  }, [session, refresh])

  useRegisterContext("networking", {
    firewalls: firewalls.length,
    vpcs: vpcs.length,
    domains: domains.length,
    floating_ips: floatingIps.length,
    droplets: droplets.length,
  })

  return (
    <AppShell>
      <div className="flex flex-col gap-4">
        <header className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
          <div>
            <h1 className="text-2xl font-semibold tracking-tight">Networking</h1>
            <p className="text-sm text-muted-foreground">
              Firewalls, VPCs, DNS, and floating IPs on the platform's underlying provider.
            </p>
          </div>
          <Button
            variant="outline"
            size="sm"
            onClick={refresh}
            disabled={loading}
            data-action="networking-refresh"
          >
            <RefreshCw className={`size-4 ${loading ? "animate-spin" : ""}`} />
            Refresh
          </Button>
        </header>

        <Tabs defaultValue="firewalls">
          <TabsList>
            <TabsTrigger value="firewalls" data-action="networking-tab-firewalls">
              Firewalls ({firewalls.length})
            </TabsTrigger>
            <TabsTrigger value="vpcs" data-action="networking-tab-vpcs">
              VPCs ({vpcs.length})
            </TabsTrigger>
            <TabsTrigger value="domains" data-action="networking-tab-domains">
              DNS ({domains.length})
            </TabsTrigger>
            <TabsTrigger value="floating-ips" data-action="networking-tab-floating-ips">
              Floating IPs ({floatingIps.length})
            </TabsTrigger>
          </TabsList>

          <TabsContent value="firewalls" className="pt-4">
            <FirewallsPanel
              firewalls={firewalls}
              loading={loading}
              error={firewallsError}
              onChanged={refresh}
            />
          </TabsContent>

          <TabsContent value="vpcs" className="pt-4">
            <VpcsPanel
              vpcs={vpcs}
              loading={loading}
              error={vpcsError}
              onRetry={refresh}
            />
          </TabsContent>

          <TabsContent value="domains" className="pt-4">
            <DomainsPanel
              domains={domains}
              loading={loading}
              error={domainsError}
              onChanged={refresh}
            />
          </TabsContent>

          <TabsContent value="floating-ips" className="pt-4">
            <FloatingIpsPanel
              ips={floatingIps}
              droplets={droplets}
              loading={loading}
              error={floatingIpsError}
              onChanged={refresh}
            />
          </TabsContent>
        </Tabs>
      </div>
    </AppShell>
  )
}

// --- Firewalls panel ---------------------------------------------------

function FirewallsPanel({
  firewalls,
  loading,
  error,
  onChanged,
}: {
  firewalls: Firewall[]
  loading: boolean
  error: unknown
  onChanged: () => Promise<void>
}) {
  const arcadia = useArcadiaClient()
  const toast = useToast()
  const [pendingDelete, setPendingDelete] = useState<Firewall | null>(null)

  return (
    <>
      <DataState
        loading={loading}
        error={error}
        isEmpty={firewalls.length === 0}
        onRetry={onChanged}
        loadingLabel="Loading firewalls…"
        empty={
          <Card>
            <CardContent>
              <EmptyState
                icon={<Shield className="size-6" />}
                title="No firewalls."
                description="Create a firewall on your provider, or configure DigitalOcean access in arcadia's .env to see existing ones."
                className="py-8"
              />
            </CardContent>
          </Card>
        }
      >
        <ul className="grid grid-cols-1 gap-3 lg:grid-cols-2">
          {firewalls.map((f) => (
          <Card key={String(f.id)}>
            <CardHeader className="flex flex-row items-center justify-between gap-3">
              <div className="flex items-center gap-2">
                <Shield className="size-4 text-muted-foreground" />
                <CardTitle className="text-base">{f.name}</CardTitle>
                {f.status ? <Badge variant="secondary">{f.status}</Badge> : null}
              </div>
              <Button
                variant="ghost"
                size="sm"
                onClick={() => setPendingDelete(f)}
                data-action={`firewall-${f.id}-delete`}
              >
                <Trash2 className="size-3.5" />
              </Button>
            </CardHeader>
            <CardContent className="text-xs text-muted-foreground">
              Inbound rules: {f.inbound_rules?.length ?? 0} · Outbound rules:{" "}
              {f.outbound_rules?.length ?? 0} · Droplets attached:{" "}
              {f.droplet_ids?.length ?? 0}
            </CardContent>
          </Card>
          ))}
        </ul>
      </DataState>

      <ConfirmDialog
        open={pendingDelete !== null}
        onOpenChange={(o) => !o && setPendingDelete(null)}
        title="Delete firewall?"
        description={
          pendingDelete
            ? `${pendingDelete.name} will be removed. Attached droplets lose this rule set.`
            : ""
        }
        confirmLabel="Delete"
        variant="danger"
        onConfirm={async () => {
          if (!pendingDelete) return
          const name = pendingDelete.name
          try {
            await deleteFirewall(arcadia, pendingDelete.id)
            setPendingDelete(null)
            await onChanged()
            toast.success(`Deleted firewall ${name}`)
          } catch (err) {
            setPendingDelete(null)
            toast.error(errorMessage(err, `delete firewall ${name}`))
          }
        }}
      />
    </>
  )
}

// --- VPCs panel --------------------------------------------------------

function VpcsPanel({
  vpcs,
  loading,
  error,
  onRetry,
}: {
  vpcs: Vpc[]
  loading: boolean
  error: unknown
  onRetry: () => void
}) {
  return (
    <DataState
      loading={loading}
      error={error}
      isEmpty={vpcs.length === 0}
      onRetry={onRetry}
      loadingLabel="Loading VPCs…"
      empty={
        <Card>
          <CardContent>
            <EmptyState
              icon={<Network className="size-6" />}
              title="No VPCs."
              description="Read-only view; create VPCs on your provider directly."
              className="py-8"
            />
          </CardContent>
        </Card>
      }
    >
      <ul className="grid grid-cols-1 gap-3 lg:grid-cols-2">
        {vpcs.map((v) => (
          <Card key={v.id}>
            <CardHeader className="flex flex-row items-center justify-between">
              <div className="flex items-center gap-2">
                <Network className="size-4 text-muted-foreground" />
                <CardTitle className="text-base">{v.name}</CardTitle>
                {v.default ? <Badge>default</Badge> : null}
              </div>
            </CardHeader>
            <CardContent className="text-xs text-muted-foreground">
              <div>
                Region: <code className="font-mono">{v.region ?? "—"}</code>
              </div>
              <div>
                IP range: <code className="font-mono">{v.ip_range ?? "—"}</code>
              </div>
            </CardContent>
          </Card>
        ))}
      </ul>
    </DataState>
  )
}

// --- Domains + DNS records panel ---------------------------------------

function DomainsPanel({
  domains,
  loading,
  error,
  onChanged,
}: {
  domains: Domain[]
  loading: boolean
  error: unknown
  onChanged: () => Promise<void>
}) {
  const arcadia = useArcadiaClient()
  const toast = useToast()
  const [selectedName, setSelectedName] = useState<string>(() => domains[0]?.name ?? "")
  const [records, setRecords] = useState<DnsRecord[]>([])
  const [loadingRecords, setLoadingRecords] = useState(false)
  // The record list loads separately from the domain list, so it carries its
  // own error: a 500 on records must not claim the domain has no records.
  const [recordsError, setRecordsError] = useState<unknown>(null)
  const [createOpen, setCreateOpen] = useState(false)
  const [pendingDelete, setPendingDelete] = useState<DnsRecord | null>(null)

  useEffect(() => {
    if (!selectedName && domains.length > 0) setSelectedName(domains[0].name)
  }, [domains, selectedName])

  const loadRecords = useCallback(
    async (name: string) => {
      if (!name) {
        setRecords([])
        return
      }
      setRecordsError(null)
      setLoadingRecords(true)
      try {
        setRecords(await listDnsRecords(arcadia, name))
      } catch (err) {
        setRecords([])
        setRecordsError(err)
      } finally {
        setLoadingRecords(false)
      }
    },
    [arcadia],
  )

  useEffect(() => {
    loadRecords(selectedName)
  }, [selectedName, loadRecords])

  if (loading || error || domains.length === 0) {
    return (
      <DataState
        loading={loading}
        error={error}
        isEmpty={domains.length === 0}
        onRetry={onChanged}
        loadingLabel="Loading domains…"
        empty={
          <Card>
            <CardContent>
              <EmptyState
                icon={<Globe className="size-6" />}
                title="No domains."
                description="Add a domain on your provider; arcadia surfaces it here for record management."
                className="py-8"
              />
            </CardContent>
          </Card>
        }
      >
        {null}
      </DataState>
    )
  }

  return (
    <Card>
      <CardHeader className="flex flex-row flex-wrap items-end gap-3">
        <div className="flex flex-col gap-1.5">
          <Label htmlFor="dns-domain" className="text-xs">
            Domain
          </Label>
          <Select value={selectedName} onValueChange={setSelectedName}>
            <SelectTrigger id="dns-domain" className="w-64" data-action="dns-domain-select">
              <SelectValue />
            </SelectTrigger>
            <SelectContent>
              {domains.map((d) => (
                <SelectItem key={d.name} value={d.name}>
                  {d.name}
                </SelectItem>
              ))}
            </SelectContent>
          </Select>
        </div>
        <div className="ml-auto flex gap-2">
          <Button
            variant="outline"
            size="sm"
            onClick={() => loadRecords(selectedName)}
            disabled={loadingRecords}
            data-action="dns-refresh"
          >
            <RefreshCw className={`size-4 ${loadingRecords ? "animate-spin" : ""}`} />
            Refresh
          </Button>
          <Button
            size="sm"
            onClick={() => setCreateOpen(true)}
            disabled={!selectedName}
            data-action="dns-create"
          >
            <Plus className="size-4" />
            New record
          </Button>
        </div>
      </CardHeader>
      <CardContent className="relative p-0">
        <DataState
          loading={loadingRecords}
          error={recordsError}
          isEmpty={records.length === 0}
          onRetry={() => loadRecords(selectedName)}
          loadingLabel="Loading DNS records…"
          empty={
            <EmptyState
              icon={<Globe className="size-6" />}
              title="No records on this domain."
              className="py-8"
            />
          }
        >
          <ul className="divide-y border-y">
            {records.map((r) => (
              <li key={String(r.id)} className="flex items-center justify-between gap-3 px-3 py-2 text-sm">
                <div className="flex items-center gap-3">
                  <Badge variant="secondary" className="font-mono text-xs">
                    {r.type}
                  </Badge>
                  <span className="font-mono text-xs">{r.name}</span>
                  <span className="text-xs text-muted-foreground">→</span>
                  <code className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs">
                    {r.data}
                  </code>
                  {r.ttl ? (
                    <span className="text-[11px] text-muted-foreground">TTL {r.ttl}s</span>
                  ) : null}
                </div>
                <Button
                  variant="ghost"
                  size="sm"
                  onClick={() => setPendingDelete(r)}
                  data-action={`dns-record-${r.id}-delete`}
                >
                  <Trash2 className="size-3.5" />
                </Button>
              </li>
            ))}
          </ul>
        </DataState>
      </CardContent>

      <DnsCreateDialog
        open={createOpen}
        domainName={selectedName}
        onClose={() => setCreateOpen(false)}
        onCreated={async (label) => {
          setCreateOpen(false)
          await loadRecords(selectedName)
          await onChanged()
          toast.success(`Created ${label}`)
        }}
      />

      <ConfirmDialog
        open={pendingDelete !== null}
        onOpenChange={(o) => !o && setPendingDelete(null)}
        title="Delete DNS record?"
        description={
          pendingDelete
            ? `${pendingDelete.type} ${pendingDelete.name} → ${pendingDelete.data}. This is destructive and may break traffic.`
            : ""
        }
        confirmLabel="Delete"
        variant="danger"
        onConfirm={async () => {
          if (!pendingDelete) return
          const label = `${pendingDelete.type} ${pendingDelete.name}`
          try {
            await deleteDnsRecord(arcadia, selectedName, pendingDelete.id)
            setPendingDelete(null)
            await loadRecords(selectedName)
            toast.success(`Deleted ${label}`)
          } catch (err) {
            setPendingDelete(null)
            toast.error(errorMessage(err, `delete ${label}`))
          }
        }}
      />
    </Card>
  )
}

function DnsCreateDialog({
  open,
  domainName,
  onClose,
  onCreated,
}: {
  open: boolean
  domainName: string
  onClose: () => void
  onCreated: (label: string) => Promise<void>
}) {
  const arcadia = useArcadiaClient()
  const [type, setType] = useState("A")
  const [name, setName] = useState("@")
  const [data, setData] = useState("")
  const [ttl, setTtl] = useState("3600")
  const [priority, setPriority] = useState("")
  const [saving, setSaving] = useState(false)
  const [error, setError] = useState<unknown>(null)

  useEffect(() => {
    if (!open) {
      setType("A")
      setName("@")
      setData("")
      setTtl("3600")
      setPriority("")
      setError(null)
    }
  }, [open])

  const submit = async () => {
    setError(null)
    setSaving(true)
    try {
      await createDnsRecord(arcadia, domainName, {
        type,
        name,
        data,
        ttl: ttl ? Number(ttl) : undefined,
        priority: priority ? Number(priority) : undefined,
      })
      await onCreated(`${type} ${name} → ${data}`)
    } catch (err) {
      // A rejected record (bad target, duplicate name) is fixable right here.
      setError(err)
    } finally {
      setSaving(false)
    }
  }

  return (
    <Dialog open={open} onOpenChange={(o) => !o && onClose()}>
      <DialogContent className="sm:max-w-md">
        <DialogHeader>
          <DialogTitle>New DNS record</DialogTitle>
          <DialogDescription>
            On <code className="font-mono">{domainName}</code>.
          </DialogDescription>
        </DialogHeader>

        <div className="grid grid-cols-2 gap-3">
          <div className="flex flex-col gap-1.5">
            <Label>Type</Label>
            <Select value={type} onValueChange={setType}>
              <SelectTrigger data-action="dns-form-type">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {DNS_TYPES.map((t) => (
                  <SelectItem key={t} value={t}>
                    {t}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="dns-name">Name</Label>
            <Input
              id="dns-name"
              value={name}
              onChange={(e) => setName(e.target.value)}
              placeholder="@ or sub"
              data-action="dns-form-name"
            />
          </div>
          <div className="col-span-2 flex flex-col gap-1.5">
            <Label htmlFor="dns-data">Data</Label>
            <Input
              id="dns-data"
              value={data}
              onChange={(e) => setData(e.target.value)}
              placeholder={
                type === "A"
                  ? "1.2.3.4"
                  : type === "CNAME"
                    ? "target.example.com."
                    : type === "TXT"
                      ? '"verification=..."'
                      : "value"
              }
              className="font-mono"
              data-action="dns-form-data"
            />
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="dns-ttl">TTL (seconds)</Label>
            <Input
              id="dns-ttl"
              type="number"
              min={30}
              value={ttl}
              onChange={(e) => setTtl(e.target.value)}
              data-action="dns-form-ttl"
            />
          </div>
          {type === "MX" || type === "SRV" ? (
            <div className="flex flex-col gap-1.5">
              <Label htmlFor="dns-priority">Priority</Label>
              <Input
                id="dns-priority"
                type="number"
                value={priority}
                onChange={(e) => setPriority(e.target.value)}
                data-action="dns-form-priority"
              />
            </div>
          ) : null}
        </div>

        {error ? <DialogError error={error} context="create the record" /> : null}

        <DialogFooter>
          <Button
            variant="outline"
            onClick={onClose}
            disabled={saving}
            data-action="dns-form-cancel"
          >
            Cancel
          </Button>
          <Button onClick={submit} disabled={saving || !data} data-action="dns-form-save">
            {saving ? <RefreshCw className="size-4 animate-spin" /> : <CheckCircle2 className="size-4" />}
            Create
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

// --- Floating IPs panel ------------------------------------------------

function FloatingIpsPanel({
  ips,
  droplets,
  loading,
  error,
  onChanged,
}: {
  ips: FloatingIp[]
  droplets: Droplet[]
  loading: boolean
  error: unknown
  onChanged: () => Promise<void>
}) {
  const arcadia = useArcadiaClient()
  const toast = useToast()
  const [assigning, setAssigning] = useState<{ ip: string; dropletId: string } | null>(null)

  return (
    <Card>
      <CardContent className="relative p-0">
        <DataState
          loading={loading}
          error={error}
          isEmpty={ips.length === 0}
          onRetry={onChanged}
          loadingLabel="Loading floating IPs…"
          empty={
            <EmptyState
              icon={<Wifi className="size-6" />}
              title="No floating IPs."
              description="Reserve a floating IP on your provider to surface it here."
              className="py-8"
            />
          }
        >
        <ul className="divide-y border-y">
          {ips.map((ip) => {
            const region =
              typeof ip.region === "string" ? ip.region : ip.region?.slug ?? "—"
            return (
              <li key={ip.ip} className="flex items-center justify-between gap-3 px-3 py-2">
                <div className="flex items-center gap-3">
                  <Wifi className="size-4 text-muted-foreground" />
                  <code className="font-mono text-sm">{ip.ip}</code>
                  <span className="text-xs text-muted-foreground">{region}</span>
                  {ip.droplet ? (
                    <Badge variant="secondary">→ {ip.droplet.name ?? ip.droplet.id}</Badge>
                  ) : (
                    <Badge>unassigned</Badge>
                  )}
                </div>
                <div className="flex gap-2">
                  {ip.droplet ? (
                    <Button
                      variant="outline"
                      size="sm"
                      onClick={async () => {
                        try {
                          await unassignFloatingIp(arcadia, ip.ip)
                          await onChanged()
                          toast.success(`Unassigned ${ip.ip}`)
                        } catch (err) {
                          toast.error(errorMessage(err, `unassign ${ip.ip}`))
                        }
                      }}
                      data-action={`fip-${ip.ip}-unassign`}
                    >
                      Unassign
                    </Button>
                  ) : (
                    <>
                      <Select
                        value={assigning?.ip === ip.ip ? assigning.dropletId : ""}
                        onValueChange={(v) => setAssigning({ ip: ip.ip, dropletId: v })}
                      >
                        <SelectTrigger
                          className="h-8 w-44"
                          data-action={`fip-${ip.ip}-droplet-select`}
                        >
                          <SelectValue placeholder="Pick droplet" />
                        </SelectTrigger>
                        <SelectContent>
                          {droplets.length === 0 ? (
                            <SelectItem value="__none" disabled>
                              No droplets
                            </SelectItem>
                          ) : (
                            droplets.map((d) => (
                              <SelectItem key={String(d.id)} value={String(d.id)}>
                                {d.name}
                              </SelectItem>
                            ))
                          )}
                        </SelectContent>
                      </Select>
                      <Button
                        size="sm"
                        disabled={
                          !assigning || assigning.ip !== ip.ip || !assigning.dropletId
                        }
                        onClick={async () => {
                          if (!assigning || assigning.ip !== ip.ip) return
                          const dropletName =
                            droplets.find((d) => String(d.id) === assigning.dropletId)
                              ?.name ?? assigning.dropletId
                          try {
                            await assignFloatingIp(arcadia, ip.ip, assigning.dropletId)
                            setAssigning(null)
                            await onChanged()
                            toast.success(`Assigned ${ip.ip} to ${dropletName}`)
                          } catch (err) {
                            toast.error(
                              errorMessage(err, `assign ${ip.ip} to ${dropletName}`),
                            )
                          }
                        }}
                        data-action={`fip-${ip.ip}-assign`}
                      >
                        Assign
                      </Button>
                    </>
                  )}
                </div>
              </li>
            )
          })}
        </ul>
        </DataState>
      </CardContent>
    </Card>
  )
}

export { RouteErrorBoundary as ErrorBoundary } from "~/components/route-error"
