import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
  AllCommunityModule, ModuleRegistry, themeQuartz,
  type ColDef,
} from 'ag-grid-community';
import { AgGridReact } from 'ag-grid-react';
import { Plus, Trash2, Radio, PlusCircle, MinusCircle, Search, UserPlus } from 'lucide-react';
import {
  Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
import { QSOEditModal } from '@/components/QSOEditModal';
import type { QSOForm } from '@/types';
import {
  NetList, NetCreate, NetRename, NetDelete, NetOpen, NetClose, NetOpenID,
  NetRoster, NetRosterUpsert, NetRosterRemove, NetLookup,
  NetActiveList, NetActivate, NetDeactivate, NetUpdateActive, NetDiscardActive,
} from '@/../wailsjs/go/main/App';
import { netctl } from '@/../wailsjs/go/models';

ModuleRegistry.registerModules([AllCommunityModule]);

const hamlogTheme = themeQuartz.withParams({
  fontFamily: 'inherit',
  fontSize: 12.5,
  backgroundColor: '#faf6ea',
  foregroundColor: '#2a2419',
  headerBackgroundColor: '#e8dfc9',
  headerTextColor: '#5a4f3a',
  headerFontWeight: 600,
  oddRowBackgroundColor: '#f5efe0',
  rowHoverColor: '#ecdcb4',
  selectedRowBackgroundColor: '#f0d9a8',
  borderColor: '#c8b994',
  rowBorder: { color: '#d8c9a8', width: 1 },
  columnBorder: { color: '#d8c9a8', width: 1 },
  cellHorizontalPadding: 10,
  rowHeight: 30,
  headerHeight: 32,
  spacing: 4,
  accentColor: '#b8410c',
  iconSize: 12,
});

type Net = netctl.Net;
type Station = netctl.Station;

function fmtTimeOn(s: any): string {
  if (!s) return '';
  const d = new Date(s);
  if (isNaN(d.getTime())) return '';
  const p = (n: number) => String(n).padStart(2, '0');
  return `${p(d.getUTCHours())}:${p(d.getUTCMinutes())}:${p(d.getUTCSeconds())}Z`;
}

const emptyStation = (): Station => netctl.Station.createFrom({ callsign: '' });

type Props = {
  onLogged?: () => void;
  countries?: string[];
  bands?: string[];
  modes?: string[];
};

export function NetControlPanel({ onLogged, countries, bands, modes }: Props) {
  const [nets, setNets] = useState<Net[]>([]);
  const [selId, setSelId] = useState<string>('');
  const [openId, setOpenId] = useState<string>('');
  const [roster, setRoster] = useState<Station[]>([]);
  const [active, setActive] = useState<QSOForm[]>([]);
  const [error, setError] = useState('');

  // Full-QSO edit modal for an on-air draft (same one Recent QSOs uses).
  const [editingDraft, setEditingDraft] = useState<QSOForm | null>(null);

  // Add/edit-contact dialog.
  const [contactOpen, setContactOpen] = useState(false);
  const [contact, setContact] = useState<Station>(emptyStation());
  const [looking, setLooking] = useState(false);

  const activeGrid = useRef<any>(null);
  const rosterGrid = useRef<any>(null);

  const isOpen = openId !== '' && openId === selId;

  const refreshNets = useCallback(async () => {
    try {
      const [list, oid] = await Promise.all([NetList(), NetOpenID()]);
      const arr = (list ?? []) as Net[];
      setNets(arr);
      setOpenId(oid ?? '');
      setSelId((cur) => cur || (oid ?? '') || (arr[0]?.id ?? ''));
    } catch (e: any) { setError(String(e?.message ?? e)); }
  }, []);

  const refreshRoster = useCallback(async (id: string) => {
    if (!id) { setRoster([]); return; }
    try { setRoster(((await NetRoster(id)) ?? []) as Station[]); }
    catch (e: any) { setError(String(e?.message ?? e)); }
  }, []);

  const refreshActive = useCallback(async () => {
    try { setActive(((await NetActiveList()) ?? []) as unknown as QSOForm[]); }
    catch { /* ignore */ }
  }, []);

  useEffect(() => { refreshNets(); }, [refreshNets]);
  useEffect(() => { refreshRoster(selId); }, [selId, refreshRoster]);
  useEffect(() => { if (isOpen) refreshActive(); else setActive([]); }, [isOpen, refreshActive]);

  // The roster side hides callsigns currently on the air (they live in the left
  // grid until logged), mirroring Log4OM's two-list behaviour.
  const activeCalls = useMemo(
    () => new Set(active.map((a) => (a.callsign ?? '').toUpperCase())),
    [active],
  );
  const rosterShown = useMemo(
    () => roster.filter((s) => !activeCalls.has((s.callsign ?? '').toUpperCase())),
    [roster, activeCalls],
  );

  async function newNet() {
    const name = window.prompt('New NET name:');
    if (!name?.trim()) return;
    try { const n = await NetCreate(name.trim()); await refreshNets(); setSelId(n.id); }
    catch (e: any) { setError(String(e?.message ?? e)); }
  }
  async function renameNet() {
    if (!selId) return;
    const cur = nets.find((n) => n.id === selId);
    const name = window.prompt('Rename NET:', cur?.name ?? '');
    if (!name?.trim()) return;
    try { await NetRename(selId, name.trim()); await refreshNets(); }
    catch (e: any) { setError(String(e?.message ?? e)); }
  }
  async function deleteNet() {
    if (!selId) return;
    const cur = nets.find((n) => n.id === selId);
    if (!window.confirm(`Delete NET "${cur?.name}" and its roster? This cannot be undone.`)) return;
    try { await NetDelete(selId); setSelId(''); await refreshNets(); }
    catch (e: any) { setError(String(e?.message ?? e)); }
  }
  async function toggleOpen() {
    try {
      if (isOpen) {
        if (active.length > 0 &&
          !window.confirm(`${active.length} station(s) still on the air will be dropped WITHOUT logging. Close anyway?`)) return;
        await NetClose(); setOpenId('');
      } else {
        await NetOpen(selId); setOpenId(selId); await refreshActive();
      }
    } catch (e: any) { setError(String(e?.message ?? e)); }
  }

  // Roster → active (start QSO).
  async function activate(call: string) {
    if (!isOpen || !call) return;
    try { await NetActivate(call); await refreshActive(); }
    catch (e: any) { setError(String(e?.message ?? e)); }
  }
  // Active → logged (end QSO, removed from session, written to the logbook).
  async function deactivate(id?: number) {
    if (id == null) return;
    try { await NetDeactivate(id); await refreshActive(); onLogged?.(); }
    catch (e: any) { setError(String(e?.message ?? e)); }
  }

  // Edit-modal handlers (operate on the in-memory draft, not the DB).
  async function saveDraft(q: QSOForm) {
    try { await NetUpdateActive(q as any); setEditingDraft(null); await refreshActive(); }
    catch (e: any) { setError(String(e?.message ?? e)); }
  }
  async function discardDraft(id: number) {
    try { await NetDiscardActive(id); setEditingDraft(null); await refreshActive(); }
    catch (e: any) { setError(String(e?.message ?? e)); }
  }

  // Add-contact dialog.
  function openAddContact() { setContact(emptyStation()); setContactOpen(true); }
  async function lookupContact() {
    const call = (contact.callsign ?? '').trim();
    if (!call) return;
    setLooking(true);
    try {
      const r = await NetLookup(call);
      setContact((c) => netctl.Station.createFrom({
        ...c, callsign: (r.callsign || call).toUpperCase(),
        name: r.name || c.name, qth: r.qth || c.qth, country: r.country || c.country,
        dxcc: r.dxcc || c.dxcc, itu: r.itu || c.itu, cq: r.cq || c.cq,
      }));
    } catch (e: any) { setError(String(e?.message ?? e)); }
    finally { setLooking(false); }
  }
  async function saveContact() {
    const call = (contact.callsign ?? '').trim().toUpperCase();
    if (!call || !selId) return;
    try {
      await NetRosterUpsert(selId, netctl.Station.createFrom({ ...contact, callsign: call }));
      setContactOpen(false);
      await refreshRoster(selId);
    } catch (e: any) { setError(String(e?.message ?? e)); }
  }
  async function removeSelectedRoster() {
    const sel = (rosterGrid.current?.api?.getSelectedRows() ?? []) as Station[];
    if (sel.length === 0) return;
    if (!window.confirm(`Remove ${sel.length} station(s) from this NET's roster?`)) return;
    try {
      for (const s of sel) await NetRosterRemove(selId, s.callsign);
      await refreshRoster(selId);
    } catch (e: any) { setError(String(e?.message ?? e)); }
  }

  const activeCols = useMemo<ColDef<QSOForm>[]>(() => [
    { headerName: 'Callsign', field: 'callsign', width: 110, cellClass: 'font-mono font-semibold' },
    { headerName: 'Name', field: 'name', flex: 1 },
    { headerName: 'QTH', field: 'qth', flex: 1 },
    { headerName: 'Time on', valueGetter: (p) => fmtTimeOn((p.data as any)?.qso_date), width: 90, cellClass: 'font-mono text-[11px]' },
    { headerName: 'Band', field: 'band', width: 70, cellClass: 'font-mono' },
    { headerName: 'Mode', field: 'mode', width: 70, cellClass: 'font-mono' },
    { headerName: 'RST S', field: 'rst_sent', width: 70, cellClass: 'font-mono' },
    { headerName: 'RST R', field: 'rst_rcvd', width: 70, cellClass: 'font-mono' },
    { headerName: 'Comment', field: 'comment', flex: 1.5 },
  ], []);

  const rosterCols = useMemo<ColDef<Station>[]>(() => [
    { headerName: 'Callsign', field: 'callsign', width: 110, cellClass: 'font-mono font-semibold' },
    { headerName: 'Name', field: 'name', flex: 1 },
    { headerName: 'QTH', field: 'qth', flex: 1 },
    { headerName: 'Country', field: 'country', width: 130 },
  ], []);

  const defaultColDef = useMemo<ColDef>(() => ({ sortable: true, resizable: true, suppressMovable: false }), []);

  return (
    <div className="flex flex-col min-h-0 flex-1">
      {/* Toolbar */}
      <div className="flex items-center gap-2 px-3 py-2 border-b border-border/60 bg-muted/30">
        <Button variant="outline" size="sm" className="h-8" onClick={newNet}>
          <Plus className="size-3.5" /> New NET
        </Button>
        <select
          className="h-8 rounded-md border border-input bg-background px-2 text-sm min-w-[180px]"
          value={selId}
          disabled={isOpen}
          onChange={(e) => setSelId(e.target.value)}
          title={isOpen ? 'Close the NET to switch' : 'Select a NET'}
        >
          <option value="">— select a NET —</option>
          {nets.map((n) => <option key={n.id} value={n.id}>{n.name}</option>)}
        </select>
        <Button variant={isOpen ? 'destructive' : 'default'} size="sm" className="h-8" disabled={!selId} onClick={toggleOpen}>
          <Radio className="size-3.5" /> {isOpen ? 'Close NET' : 'Open NET'}
        </Button>
        <Button variant="ghost" size="sm" className="h-8" disabled={!selId || isOpen} onClick={renameNet}>Rename</Button>
        <Button variant="ghost" size="sm" className="h-8 text-rose-700" disabled={!selId || isOpen} onClick={deleteNet}>
          <Trash2 className="size-3.5" /> Delete
        </Button>

        <div className="flex-1" />
        {isOpen && <Badge className="bg-emerald-600 text-white tracking-wider">NET OPEN</Badge>}
        <span className="text-[11px] text-muted-foreground">
          On air: <strong className="text-foreground">{active.length}</strong> ·
          Roster: <strong className="text-foreground">{roster.length}</strong>
        </span>
        {error && <Badge variant="destructive" className="max-w-[280px] truncate" title={error}>{error}</Badge>}
      </div>

      <div className="flex min-h-0 flex-1">
        {/* ACTIVE USERS (left) */}
        <div className="flex flex-col min-h-0 flex-1 border-r border-border/60">
          <div className="flex items-center gap-2 px-3 py-1.5 bg-red-600 text-white text-[11px] font-semibold uppercase tracking-wider">
            On air — active QSOs
            <span className="ml-auto font-normal normal-case opacity-90">double-click → edit all fields · "Log &amp; end" to save</span>
          </div>
          <div style={{ flex: 1, minHeight: 0, position: 'relative' }}>
            <div style={{ position: 'absolute', inset: 0 }}>
              <AgGridReact<QSOForm>
                ref={activeGrid}
                theme={hamlogTheme}
                rowData={active}
                columnDefs={activeCols}
                defaultColDef={defaultColDef}
                onRowDoubleClicked={(e) => e.data && setEditingDraft(e.data)}
                rowSelection={{ mode: 'singleRow', checkboxes: false, enableClickSelection: true }}
                animateRows={false}
                getRowId={(p) => String((p.data as any).id)}
              />
            </div>
          </div>
          {isOpen && active.length > 0 && (
            <div className="px-3 py-1.5 border-t border-border/60 bg-muted/30 flex items-center gap-2">
              <Button variant="ghost" size="sm" className="h-7 text-[11px]"
                onClick={() => { const r = activeGrid.current?.api?.getSelectedRows?.()?.[0] as QSOForm; if (r) deactivate(r.id as number); }}>
                <MinusCircle className="size-3.5" /> Log &amp; end selected
              </Button>
            </div>
          )}
        </div>

        {/* NET USERS / roster (right) */}
        <div className="flex flex-col min-h-0 w-[40%] max-w-[560px]">
          <div className="flex items-center gap-2 px-3 py-1.5 bg-emerald-700 text-white text-[11px] font-semibold uppercase tracking-wider">
            NET users — roster
            <span className="ml-auto font-normal normal-case opacity-90">double-click → put on air</span>
          </div>
          <div style={{ flex: 1, minHeight: 0, position: 'relative' }}>
            <div style={{ position: 'absolute', inset: 0 }}>
              <AgGridReact<Station>
                ref={rosterGrid}
                theme={hamlogTheme}
                rowData={rosterShown}
                columnDefs={rosterCols}
                defaultColDef={defaultColDef}
                rowSelection={{ mode: 'multiRow', checkboxes: false, headerCheckbox: false, enableClickSelection: true }}
                onRowDoubleClicked={(e) => e.data && activate(e.data.callsign)}
                animateRows={false}
                getRowId={(p) => String((p.data as any).callsign)}
              />
            </div>
          </div>
          <div className="px-3 py-1.5 border-t border-border/60 bg-muted/30 flex items-center gap-2">
            <Button variant="ghost" size="sm" className="h-7 text-[11px]" disabled={!selId} onClick={openAddContact}>
              <UserPlus className="size-3.5" /> Add contact
            </Button>
            <Button variant="ghost" size="sm" className="h-7 text-[11px] text-rose-700" disabled={!selId} onClick={removeSelectedRoster}>
              <MinusCircle className="size-3.5" /> Remove
            </Button>
            {isOpen && (
              <Button variant="ghost" size="sm" className="h-7 text-[11px] ml-auto"
                onClick={() => { const r = rosterGrid.current?.api?.getSelectedRows?.()?.[0] as Station; if (r) activate(r.callsign); }}>
                <PlusCircle className="size-3.5" /> Put selected on air
              </Button>
            )}
          </div>
        </div>
      </div>

      {/* Full-QSO edit modal for the selected on-air draft. Save writes back to
          the in-memory draft (NetUpdateActive); Delete cancels it (no log). */}
      {editingDraft && (
        <QSOEditModal
          qso={editingDraft}
          onSave={saveDraft}
          onDelete={discardDraft}
          onClose={() => setEditingDraft(null)}
          countries={countries}
          bands={bands}
          modes={modes}
        />
      )}

      {/* Add / edit contact dialog */}
      <Dialog open={contactOpen} onOpenChange={setContactOpen}>
        <DialogContent className="max-w-md">
          <DialogHeader>
            <DialogTitle>Add contact to NET</DialogTitle>
            <DialogDescription>Saved in this NET's roster (reused next time you open it).</DialogDescription>
          </DialogHeader>
          <div className="grid gap-3 px-5 py-2">
            <div className="flex items-end gap-2">
              <div className="flex-1">
                <Label className="text-[11px]">Callsign</Label>
                <Input className="font-mono uppercase" value={contact.callsign ?? ''}
                  onChange={(e) => setContact((c) => netctl.Station.createFrom({ ...c, callsign: e.target.value.toUpperCase() }))}
                  onKeyDown={(e) => { if (e.key === 'Enter') lookupContact(); }} />
              </div>
              <Button variant="outline" size="sm" className="h-9" onClick={lookupContact} disabled={looking || !(contact.callsign ?? '').trim()}>
                <Search className="size-3.5" /> {looking ? '…' : 'Search'}
              </Button>
            </div>
            <div>
              <Label className="text-[11px]">Name</Label>
              <Input value={contact.name ?? ''} onChange={(e) => setContact((c) => netctl.Station.createFrom({ ...c, name: e.target.value }))} />
            </div>
            <div>
              <Label className="text-[11px]">QTH</Label>
              <Input value={contact.qth ?? ''} onChange={(e) => setContact((c) => netctl.Station.createFrom({ ...c, qth: e.target.value }))} />
            </div>
            <div>
              <Label className="text-[11px]">Country</Label>
              <Input value={contact.country ?? ''} onChange={(e) => setContact((c) => netctl.Station.createFrom({ ...c, country: e.target.value }))} />
            </div>
          </div>
          <DialogFooter>
            <Button variant="ghost" size="sm" onClick={() => setContactOpen(false)}>Cancel</Button>
            <Button size="sm" onClick={saveContact} disabled={!(contact.callsign ?? '').trim()}>
              <PlusCircle className="size-3.5" /> Save in NET
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </div>
  );
}
