import { useCallback, useEffect, useState, type ReactNode } from 'react';
import {
  LayoutDashboard, Boxes, FileWarning, KeyRound, BookCheck, EyeOff, History as HistoryIcon, Wrench, Info,
} from 'lucide-react';
import { Tabs, TabsContent, TabsList, TabsTrigger, TabsHighlight, TabsHighlightItem } from '@/components/ui/tabs';
import { PageMasthead, type MastheadTone } from '@/components/ui/PageMasthead';
import { CapabilityGate } from '@/components/CapabilityGate';
import { deriveMasthead, SCANNER_DETECTIONS_NOTE } from './security/securityMasthead';
import { springs } from '@/lib/motion';
import { apiFetch } from '@/lib/api';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { formatTimeAgo } from '@/lib/relativeTime';
import { useAuth } from '@/context/AuthContext';
import { useNodes } from '@/context/NodeContext';
import { useImageScan } from '@/hooks/useImageScan';
import { useIsMobile } from '@/hooks/use-is-mobile';
import { Masthead, type Tone } from './mobile/mobile-ui';
import { SecurityMobileTabs, type SecurityMobileTab } from './security/SecurityMobile';
import { SENCHO_SETTINGS_CHANGED, type SecurityTab, type SenchoSettingsChangedDetail } from '@/lib/events';
import type { ImageFilterValue } from '@/lib/severityStyles';
import type { SecurityOverview, ScanSummary, ScanDetailTab, SecurityRiskTrendPoint, ExploitIntelFinding, FleetRole, PostureReasonKind } from '@/types/security';
import { VulnerabilityScanSheet } from './VulnerabilityScanSheet';
import { SuppressionsPanel } from './settings/SuppressionsPanel';
import { MisconfigAckPanel } from './settings/MisconfigAckPanel';
import { OverviewTab } from './security/OverviewTab';
import { reasonImageFilter } from './security/postureNavigation';
import {
  targetingFromTargets,
  type ImagesTargetingInput,
  type ImagesTargetingState,
} from './security/imagesTargeting';
import { ImagesTab } from './security/ImagesTab';
import { FindingsTab } from './security/FindingsTab';
import { ScanPolicyManager } from './security/ScanPolicyManager';
import { ScannerSetupTab } from './security/ScannerSetupTab';
import { HistoryTab } from './security/HistoryTab';

/** A /security/image-summaries 200 body must be a map of scan summaries. An
 *  unexpected shape is treated as an error, never as a benign "no findings". An
 *  empty object is valid (a node with no scans yet). */
function isScanSummaryMap(v: unknown): v is Record<string, ScanSummary> {
  if (!v || typeof v !== 'object' || Array.isArray(v)) return false;
  return Object.values(v).every(
    (s) =>
      !!s && typeof s === 'object'
      && typeof (s as ScanSummary).image_ref === 'string'
      && typeof (s as ScanSummary).scan_id === 'number',
  );
}

interface SecurityViewProps {
  activeTab: SecurityTab;
  onTabChange: (tab: SecurityTab) => void;
  /** Notifications + more-menu cluster for the mobile masthead right slot.
   *  Passed only on the bespoke phone surface; absent on desktop. */
  headerActions?: ReactNode;
}

// Maps the masthead tone (shared with the desktop PageMasthead) onto the mobile
// masthead's dot tone, state-word color, and whether the dot pulses. Idle reads
// as an amber caution (the mobile dot has no neutral grey).
type StateWordClass = 'text-destructive' | 'text-warning' | 'text-stat-value' | 'text-stat-title';
const MOBILE_MASTHEAD_TONE: Record<MastheadTone, { dot: Tone; word: StateWordClass; pulse: boolean }> = {
  error: { dot: 'destructive', word: 'text-destructive', pulse: true },
  warn: { dot: 'warning', word: 'text-warning', pulse: true },
  live: { dot: 'brand', word: 'text-stat-value', pulse: false },
  idle: { dot: 'warning', word: 'text-stat-title', pulse: false },
};

export function SecurityView({ activeTab, onTabChange, headerActions }: SecurityViewProps) {
  const { can } = useAuth();
  const { activeNode } = useNodes();
  const isMobile = useIsMobile();
  const isRemote = activeNode?.type === 'remote';

  const [overview, setOverview] = useState<SecurityOverview | null>(null);
  // 'unsupported' = the node has no overview endpoint (e.g. an older remote, 404);
  // 'failed' = a genuine error (5xx, network, malformed body) that must not read as benign.
  const [overviewLoadError, setOverviewLoadError] = useState<'unsupported' | 'failed' | null>(null);
  const [summaries, setSummaries] = useState<Record<string, ScanSummary>>({});
  const [summariesLoading, setSummariesLoading] = useState(true);
  const [summariesError, setSummariesError] = useState(false);
  const [trend, setTrend] = useState<SecurityRiskTrendPoint[]>([]);
  const [exploitIntel, setExploitIntel] = useState<ExploitIntelFinding[]>([]);
  // True when the exploit-intel query hit its row cap: the list shows the
  // highest-risk findings but not every one, so the UI discloses it.
  const [exploitTruncated, setExploitTruncated] = useState(false);
  const [isReplica, setIsReplica] = useState(false);
  // Bumped after a node-wide scan completes to refetch the active node's posture.
  const [reloadToken, setReloadToken] = useState(0);

  const [inspectScanId, setInspectScanId] = useState<number | null>(null);
  const [inspectInitialTab, setInspectInitialTab] = useState<ScanDetailTab | undefined>(undefined);
  const [inspectDriverVulnerabilityIds, setInspectDriverVulnerabilityIds] = useState<string[] | undefined>(undefined);
  // Filter / targeting for the Images tab when arriving from an overview link.
  // SecurityView owns both; ImagesTab never clears targeting locally (R1).
  const [imagesFilter, setImagesFilter] = useState<ImageFilterValue | null>(null);
  const [imagesFilterToken, setImagesFilterToken] = useState(0);
  const [imagesTargeting, setImagesTargeting] = useState<ImagesTargetingState | null>(null);

  // Navigate between security tabs, optionally preselecting an Images filter
  // and/or posture targeting so an overview action lands on the affected images.
  const handleNavigate = useCallback((
    tab: SecurityTab,
    filter?: ImageFilterValue,
    targeting?: ImagesTargetingInput,
  ) => {
    if (tab === 'images') {
      if (targeting && targeting.imageRefs.length > 0) {
        setImagesTargeting((prev) => ({
          kind: targeting.kind,
          label: targeting.label,
          imageRefs: targeting.imageRefs,
          targets: targeting.targets,
          ...(targeting.drivers ? { drivers: targeting.drivers } : {}),
          ...(targeting.driverCount !== undefined ? { driverCount: targeting.driverCount } : {}),
          ...(targeting.driversTruncated !== undefined
            ? { driversTruncated: targeting.driversTruncated }
            : {}),
          token: (prev?.token ?? 0) + 1,
        }));
        // R2: targeting navigation resets severity unless an explicit filter is supplied.
        setImagesFilter(filter ?? null);
        setImagesFilterToken((t) => t + 1);
      } else {
        setImagesTargeting(null);
        if (filter) {
          setImagesFilter(filter);
          setImagesFilterToken((t) => t + 1);
        }
      }
    }
    onTabChange(tab);
  }, [onTabChange]);

  const clearImagesTargeting = useCallback(() => {
    setImagesTargeting(null);
  }, []);

  // Drop Images drill-down state when the active node changes so refs from
  // node A never filter node B's summaries.
  useEffect(() => {
    setImagesTargeting(null);
    setImagesFilter(null);
    setImagesFilterToken(0);
  }, [activeNode?.id]);

  const onInspect = useCallback((
    scanId: number,
    initialTab?: ScanDetailTab,
    driverVulnerabilityIds?: string[],
  ) => {
    setInspectInitialTab(initialTab);
    setInspectDriverVulnerabilityIds(driverVulnerabilityIds);
    setInspectScanId(scanId);
  }, []);

  // Scanner readiness gates the Images Actions column; an admin on a node whose
  // scanner is available can trigger scans inline.
  const canScanImages = can('stack:deploy') && !!overview?.scanner.available;
  const canScanNode = can('node:manage') && !!overview?.scanner.available;
  const canReadSecurityExports = can('stack:read');
  const canEditSecurityPolicy = can('stack:edit');
  const { scanningRef, scanImage } = useImageScan({
    onComplete: (scanId) => onInspect(scanId, 'vulns'),
    onSummaries: setSummaries,
  });

  // Active-node scoped data: overview rollup + image summaries follow x-node-id.
  // A failed fetch (5xx, network, malformed body) must surface as an error, never
  // as a benign "clean / no findings" view, which for a security surface is the
  // most dangerous misread. A 404 on /overview is the one benign case (an older
  // remote node that lacks the endpoint).
  useEffect(() => {
    let cancelled = false;
    (async () => {
      setSummariesLoading(true);
      setOverviewLoadError(null);
      setSummariesError(false);
      // The trend chart is non-critical: isolate its fetch entirely (transport
      // failure included, not just a non-OK/malformed body) so it can never
      // poison the overview/summaries error state. It degrades to an empty chart
      // with its own "no history" message.
      const trendPromise: Promise<SecurityRiskTrendPoint[]> = apiFetch('/security/overview/trend')
        .then((r) => (r.ok ? r.json() : []))
        .then((t) => (Array.isArray(t) ? t : []))
        .catch(() => []);
      // Exploit-intel powers two overview charts; isolate it like the trend so a
      // failure (or an older node without the endpoint) degrades to empty panels.
      const exploitIntelPromise: Promise<{ items: ExploitIntelFinding[]; truncated: boolean }> = apiFetch('/security/overview/exploit-intel')
        .then((r) => (r.ok ? r.json() : { items: [], truncated: false }))
        .then((b) => ({
          items: b && Array.isArray(b.items) ? b.items : [],
          truncated: b?.truncated === true,
        }))
        .catch(() => ({ items: [], truncated: false }));
      try {
        const [overviewRes, summariesRes] = await Promise.all([
          apiFetch('/security/overview', { cache: 'no-store' }),
          apiFetch('/security/image-summaries', { cache: 'no-store' }),
        ]);
        if (cancelled) return;
        if (overviewRes.ok) {
          setOverview(await overviewRes.json());
        } else {
          setOverview(null);
          setOverviewLoadError(overviewRes.status === 404 ? 'unsupported' : 'failed');
          if (overviewRes.status !== 404) {
            console.warn('[Security] overview request failed:', overviewRes.status);
          }
        }
        if (summariesRes.ok) {
          const body = await summariesRes.json();
          if (isScanSummaryMap(body)) {
            setSummaries(body);
          } else {
            // A 200 with an unexpected shape must not read as "no findings".
            setSummaries({});
            setSummariesError(true);
            console.warn('[Security] image-summaries returned an unexpected shape');
          }
        } else {
          setSummaries({});
          setSummariesError(true);
          console.warn('[Security] image-summaries request failed:', summariesRes.status);
        }
      } catch (err) {
        if (cancelled) return;
        console.warn('[Security] failed to load security data:', err);
        setOverview(null);
        setOverviewLoadError('failed');
        setSummaries({});
        setSummariesError(true);
      } finally {
        if (!cancelled) setSummariesLoading(false);
      }
      const [trendData, intelData] = await Promise.all([trendPromise, exploitIntelPromise]);
      if (!cancelled) {
        setTrend(trendData);
        setExploitIntel(intelData.items);
        setExploitTruncated(intelData.truncated);
      }
    })();
    return () => { cancelled = true; };
  }, [activeNode?.id, reloadToken]);

  // Toggling image-update checks flips overview.updateChecksDisabled, which
  // gates Check again and the uncertain-row description. Refetch so Overview
  // does not stay stale until a page reload.
  useEffect(() => {
    const handler = (e: Event) => {
      const keys = (e as CustomEvent<SenchoSettingsChangedDetail>).detail?.changedKeys ?? [];
      if (keys.includes('image_update_checks_enabled')) {
        setReloadToken((t) => t + 1);
      }
    };
    window.addEventListener(SENCHO_SETTINGS_CHANGED, handler);
    return () => window.removeEventListener(SENCHO_SETTINGS_CHANGED, handler);
  }, []);

  // Governance panels (suppressions/acks) are control-governed; probe the local
  // fleet role so a replica renders them read-only, mirroring Settings.
  useEffect(() => {
    if (isRemote) return;
    let cancelled = false;
    (async () => {
      try {
        const res = await apiFetch('/fleet/role', { localOnly: true });
        if (!res.ok || cancelled) return;
        const data = await res.json();
        if (!cancelled && (data?.role === 'control' || data?.role === 'replica')) {
          setIsReplica((data.role as FleetRole) === 'replica');
        }
      } catch {
        // Treat as control on probe failure (read-only gate is best-effort).
      }
    })();
    return () => { cancelled = true; };
  }, [isRemote, activeNode?.id]);

  const { state, tone } = deriveMasthead(overview, overviewLoadError !== null);
  const pulsing = tone === 'live' && !!overview?.scanner.available;

  // The mobile tab strip mirrors the desktop tab IA, so every section stays
  // reachable by scroll.
  const mobileTabs: SecurityMobileTab[] = [
    { value: 'overview', label: 'Overview' },
    { value: 'images', label: 'Images' },
    { value: 'compose', label: 'Compose risks' },
    { value: 'secrets', label: 'Secrets' },
    { value: 'policies', label: 'Policies' },
    { value: 'suppressions', label: 'Suppressions' },
    { value: 'history', label: 'History' },
    { value: 'scanner', label: 'Scanner setup' },
  ];

  // The scanner-detections disclaimer rides as an info affordance next to the
  // scanned-images count rather than a standing caption below the masthead.
  // When posture is Action needed or Monitoring, the subtitle leads with the
  // residual Crit/High count and top reason labels so the operator sees why
  // without opening the review queue.
  const blockers = overview?.postureReasons?.filter((r) => r.severity === 'blocker') ?? [];
  const reviewReasons = overview?.postureReasons?.filter((r) => r.severity === 'review') ?? [];
  const residualCritHigh = (overview?.rawCritical ?? overview?.critical ?? 0)
    + (overview?.rawHigh ?? overview?.high ?? 0);
  let actionSummary: string | null = null;
  if (overview?.posture === 'Action needed' && blockers.length > 0) {
    actionSummary = `${blockers.length} action${blockers.length === 1 ? '' : 's'}: ${blockers.slice(0, 2).map((r) => r.label.toLowerCase()).join(', ')} · `;
  } else if (overview?.posture === 'Monitoring' && residualCritHigh > 0) {
    const labels = (reviewReasons.length > 0 ? reviewReasons : overview.postureReasons ?? [])
      .slice(0, 2)
      .map((r) => r.label.toLowerCase());
    const labelPart = labels.length > 0 ? `: ${labels.join(', ')}` : '';
    actionSummary = `${residualCritHigh} residual Crit/High${labelPart} · `;
  }
  const subtitle = overview ? (
    <span className="inline-flex items-center gap-1.5">
      {actionSummary ? <span>{actionSummary}</span> : null}
      <span>
        {overview.scannedImages} {overview.scannedImages === 1 ? 'image' : 'images'} scanned · scanner {overview.scanner.available ? 'ready' : 'not installed'}
      </span>
      <TooltipProvider>
        <Tooltip>
          <TooltipTrigger asChild>
            <span className="inline-flex shrink-0 cursor-help text-stat-subtitle/70 hover:text-stat-subtitle">
              <Info className="h-3 w-3" strokeWidth={1.5} aria-hidden="true" />
            </span>
          </TooltipTrigger>
          <TooltipContent className="max-w-[260px]">{SCANNER_DETECTIONS_NOTE}</TooltipContent>
        </Tooltip>
      </TooltipProvider>
    </span>
  ) : undefined;

  // The tab panels are identical on desktop and mobile; only the masthead and
  // the tab strip differ, so the panels are shared between both layouts.
  const tabPanels = (
    <>
        <TabsContent value="overview">
          <OverviewTab
            overview={overview}
            loadError={overviewLoadError}
            trend={trend}
            exploitIntel={exploitIntel}
            exploitTruncated={exploitTruncated}
            onNavigate={handleNavigate}
            onInspect={onInspect}
            canScan={canScanNode}
            canManageNode={!!activeNode?.id && can('node:manage', 'node', String(activeNode.id))}
            onScanComplete={() => setReloadToken((t) => t + 1)}
          />
        </TabsContent>

        <TabsContent value="images">
          <CapabilityGate capability="vulnerability-scanning" featureName="Vulnerability scanning" resolution="Install a scanner from the Scanner setup tab.">
            <ImagesTab
              summaries={summaries}
              loading={summariesLoading}
              error={summariesError}
              onInspect={onInspect}
              canScan={canScanImages}
              scanningRef={scanningRef}
              onScan={scanImage}
              initialFilter={imagesFilter ?? undefined}
              filterToken={imagesFilterToken}
              targeting={imagesTargeting}
              onClearTargeting={clearImagesTargeting}
              posturePartial={overview?.posturePartial === true}
              nodeId={activeNode?.id}
            />
          </CapabilityGate>
        </TabsContent>

        <TabsContent value="compose">
          <CapabilityGate capability="vulnerability-scanning" featureName="Vulnerability scanning" resolution="Install a scanner from the Scanner setup tab.">
            <FindingsTab kind="misconfig" summaries={summaries} loading={summariesLoading} error={summariesError} onInspect={onInspect} />
          </CapabilityGate>
        </TabsContent>

        <TabsContent value="secrets">
          <CapabilityGate capability="vulnerability-scanning" featureName="Vulnerability scanning" resolution="Install a scanner from the Scanner setup tab.">
            <FindingsTab kind="secret" summaries={summaries} loading={summariesLoading} error={summariesError} onInspect={onInspect} />
          </CapabilityGate>
        </TabsContent>

        <TabsContent value="policies">
          <ScanPolicyManager />
        </TabsContent>

        <TabsContent value="suppressions">
          {isRemote ? (
            <div className="flex items-start gap-2 rounded-lg border border-card-border bg-muted/30 px-4 py-3">
              <Info className="w-4 h-4 text-muted-foreground shrink-0 mt-0.5" strokeWidth={1.5} aria-hidden="true" />
              <div className="text-sm">
                <div className="font-medium">Managed on the local instance</div>
                <p className="text-xs text-muted-foreground mt-0.5">
                  Suppressions and acknowledgements are managed on the local Sencho instance. Switch to the local node to view them.
                </p>
              </div>
            </div>
          ) : (
            <div className="space-y-6">
              <SuppressionsPanel isReplica={isReplica} />
              <MisconfigAckPanel isReplica={isReplica} />
            </div>
          )}
        </TabsContent>

        <TabsContent value="history">
          <CapabilityGate capability="vulnerability-scanning" featureName="Vulnerability scanning" resolution="Install a scanner from the Scanner setup tab.">
            <HistoryTab onInspect={onInspect} />
          </CapabilityGate>
        </TabsContent>

        <TabsContent value="scanner">
          <ScannerSetupTab />
        </TabsContent>
    </>
  );

  const scanSheet = (
    <VulnerabilityScanSheet
      scanId={inspectScanId}
      initialTab={inspectInitialTab}
      driverVulnerabilityIds={inspectDriverVulnerabilityIds}
      driverFilterMode={
        imagesTargeting?.kind === 'waiting_upstream' || imagesTargeting?.kind === 'update_check_uncertain'
          ? 'monitoring'
          : 'action'
      }
      driverCount={imagesTargeting?.driverCount}
      driversTruncated={imagesTargeting?.driversTruncated}
      onClose={() => {
        setInspectScanId(null);
        setInspectDriverVulnerabilityIds(undefined);
      }}
      canGenerateSbom={canReadSecurityExports}
      canExportSarif={canReadSecurityExports}
      canCompare
      canManageSuppressions={canEditSecurityPolicy}
    />
  );

  // Mobile: a bespoke masthead-led screen (no TopBar). The masthead leads with
  // the notifications + more-menu cluster in its right slot, the tab strip is a
  // horizontal scroller, and the active panel scrolls below.
  if (isMobile) {
    const mobileTone = MOBILE_MASTHEAD_TONE[tone];
    return (
      <div className="flex h-full min-h-0 flex-col">
        <Masthead
          kicker="security"
          state={state}
          stateTone={mobileTone.dot}
          stateClassName={mobileTone.word}
          live={mobileTone.pulse}
          meta={subtitle}
          right={headerActions}
        />
        <SecurityMobileTabs tabs={mobileTabs} active={activeTab} onSelect={onTabChange} />
        <div className="flex-1 min-h-0 overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden p-4">
          <Tabs value={activeTab} onValueChange={(v) => onTabChange(v as SecurityTab)}>
            {tabPanels}
          </Tabs>
        </div>
        {scanSheet}
      </div>
    );
  }

  return (
    <div className="h-full overflow-auto p-6">
      <PageMasthead
        state={state}
        tone={tone}
        pulsing={pulsing}
        size="hero"
        className="rounded-lg mb-4"
        subtitle={subtitle}
        metadata={overview ? [
          { label: 'CRITICAL', value: String(overview.critical), tone: overview.critical > 0 ? 'error' : 'value' },
          { label: 'HIGH', value: String(overview.high), tone: overview.high > 0 ? 'warn' : 'value' },
          { label: 'LAST SCAN', value: overview.lastSuccessfulScanAt ? formatTimeAgo(overview.lastSuccessfulScanAt) : 'never', tone: 'subtitle' },
        ] : undefined}
      >
        {overview?.posture === 'Action needed' && overview.primaryAction ? (
          <button
            type="button"
            onClick={() => {
              const action = overview.primaryAction!;
              const blockerLabel = overview.postureReasons?.find(
                (r) => r.kind === action.kind && r.severity === 'blocker',
              )?.label ?? action.label;
              const targeting = targetingFromTargets(
                action.kind as PostureReasonKind,
                blockerLabel,
                action.targets,
                action.drivers,
                { driverCount: action.driverCount, driversTruncated: action.driversTruncated },
              );
              const filter = targeting ? undefined : reasonImageFilter(action.kind);
              handleNavigate(action.targetTab, filter, targeting);
            }}
            className="text-xs font-medium text-brand hover:underline whitespace-nowrap"
          >
            {overview.primaryAction.label} →
          </button>
        ) : null}
      </PageMasthead>

      <Tabs value={activeTab} onValueChange={(v) => onTabChange(v as SecurityTab)}>
        {/* Standard full-width tab band (matches Fleet): the list's own pill band
            is flattened so the tabs sit directly in this single band. */}
        <div className="flex items-center gap-3 mb-4 flex-wrap rounded-lg border border-card-border bg-card/40 px-2.5 py-1.5">
          <TabsList className="border-transparent bg-transparent max-md:w-full max-md:overflow-x-auto max-md:[scrollbar-width:none]">
            <TabsHighlight className="rounded-md bg-brand/20" transition={springs.snappy}>
            <TabsHighlightItem value="overview">
              <TabsTrigger value="overview"><LayoutDashboard className="w-4 h-4 mr-1.5" />Overview</TabsTrigger>
            </TabsHighlightItem>
            <TabsHighlightItem value="images">
              <TabsTrigger value="images"><Boxes className="w-4 h-4 mr-1.5" />Images</TabsTrigger>
            </TabsHighlightItem>
            <TabsHighlightItem value="compose">
              <TabsTrigger value="compose"><FileWarning className="w-4 h-4 mr-1.5" />Compose risks</TabsTrigger>
            </TabsHighlightItem>
            <TabsHighlightItem value="secrets">
              <TabsTrigger value="secrets"><KeyRound className="w-4 h-4 mr-1.5" />Secrets</TabsTrigger>
            </TabsHighlightItem>
            <span aria-hidden className="self-center mx-1 h-4 w-px bg-border" />
            <TabsHighlightItem value="policies">
              <TabsTrigger value="policies"><BookCheck className="w-4 h-4 mr-1.5" />Policies</TabsTrigger>
            </TabsHighlightItem>
            <TabsHighlightItem value="suppressions">
              <TabsTrigger value="suppressions"><EyeOff className="w-4 h-4 mr-1.5" />Suppressions</TabsTrigger>
            </TabsHighlightItem>
            <TabsHighlightItem value="history">
              <TabsTrigger value="history"><HistoryIcon className="w-4 h-4 mr-1.5" />History</TabsTrigger>
            </TabsHighlightItem>
            <TabsHighlightItem value="scanner">
              <TabsTrigger value="scanner"><Wrench className="w-4 h-4 mr-1.5" />Scanner setup</TabsTrigger>
            </TabsHighlightItem>
          </TabsHighlight>
          </TabsList>
        </div>
        {tabPanels}
      </Tabs>

      {scanSheet}
    </div>
  );
}
