import type { Node, Edge } from '@xyflow/react'
import type { SourceNodeData } from './nodes'
import type { PlatformNodeData } from './nodes'
import type { TenantNodeData } from './nodes'
import type { ConnectorNodeData } from './nodes'
import type { LibraryNodeData } from './nodes'

// ── Public data interface ───────────────────────────────────────────────────

export interface TopologyLayoutData {
  sources: {
    name: string
    icon: string
    cadence: string
    ruleCount: number
    health: 'healthy' | 'warning' | 'error'
  }[]
  tenants: {
    name: string
    color: string
    status: 'active' | 'degraded' | 'offline'
    requests24h: number
    detections: number
    connectors: {
      name: string
      type: 'siem' | 'edr' | 'intel' | 'validation' | 'framework'
      status: 'connected' | 'degraded' | 'disconnected'
    }[]
  }[]
  libraryStats: {
    totalDetections: number
    actorsTracked: number
    intelReports: number
    techniquesCataloged: number
    techniquesTotal: number
    classificationBacklog: number
  }
  tenantSourceMap?: Record<string, string[]>
  tenantLibraryMap?: Record<string, string[]>
}

// ── Static lookups ──────────────────────────────────────────────────────────

const categoryOf: Record<string, { category: string; label: string }> = {
  SigmaHQ: { category: 'detection', label: 'Detection Source' },
  'YARA Rules': { category: 'detection', label: 'Detection Source' },
  'ET/Snort': { category: 'detection', label: 'Detection Source' },
  'Detection Rules': { category: 'detection', label: 'Detection Source' },
  'MITRE ATT&CK': { category: 'framework', label: 'Framework' },
  'OpenCTI Intel': { category: 'intel', label: 'Intel Source' },
  'Threat Intel': { category: 'intel', label: 'Intel Source' },
}

const sourceCountLabel: Record<string, string> = {
  SigmaHQ: 'rules',
  'YARA Rules': 'rules',
  'ET/Snort': 'rules',
  'Detection Rules': 'rules',
  'MITRE ATT&CK': 'techniques',
  'OpenCTI Intel': 'reports',
  'Threat Intel': 'reports',
}

// Column X positions
const COL_SOURCE = 0
const COL_PIPELINE = 320
const COL_LIBRARY = 600
const COL_TENANT = 920
const COL_CONNECTOR = 1200

// ── Library definitions builder ─────────────────────────────────────────────

interface LibraryDef {
  id: string
  name: string
  category: 'detection' | 'actor' | 'intel' | 'framework' | 'opportunity'
  metric: string
}

function buildLibraryDefs(stats: TopologyLayoutData['libraryStats']): LibraryDef[] {
  return [
    {
      id: 'lib-detections',
      name: 'Detection Library',
      category: 'detection',
      metric: `${stats.totalDetections.toLocaleString()} rules`,
    },
    {
      id: 'lib-actors',
      name: 'Actor Library',
      category: 'actor',
      metric: `${stats.actorsTracked.toLocaleString()} actors`,
    },
    {
      id: 'lib-intel',
      name: 'Intel Reports',
      category: 'intel',
      metric: `${stats.intelReports.toLocaleString()} reports`,
    },
    {
      id: 'lib-attack',
      name: 'ATT&CK Coverage',
      category: 'framework',
      metric: `${stats.techniquesCataloged.toLocaleString()} / ${stats.techniquesTotal.toLocaleString()} techniques`,
    },
    {
      id: 'lib-opportunities',
      name: 'Opportunities',
      category: 'opportunity',
      metric: `${stats.classificationBacklog.toLocaleString()} gaps`,
    },
  ]
}

// ── Node builders ───────────────────────────────────────────────────────────

function makeSourceNode(source: TopologyLayoutData['sources'][number], x: number, y: number): Node {
  const cat = categoryOf[source.name] ?? { category: 'detection', label: 'Detection Source' }
  return {
    id: `src-${source.name}`,
    type: 'source',
    position: { x, y },
    width: 185,
    height: 72,
    data: {
      name: source.name,
      icon: source.icon,
      category: cat.category,
      categoryLabel: cat.label,
      health: source.health,
      ruleCount: source.ruleCount,
      countLabel: sourceCountLabel[source.name] ?? 'rules',
      cadence: source.cadence,
    } satisfies SourceNodeData,
  }
}

function makePipelineNode(centerY: number): Node {
  return {
    id: 'platform-pipeline',
    type: 'platform',
    position: { x: COL_PIPELINE, y: centerY - 45 },
    width: 200,
    height: 90,
    data: {
      name: 'Collection Pipeline',
      role: 'Processing',
      metric: 'Healthy',
    } satisfies PlatformNodeData,
  }
}

function makeLibraryNode(def: LibraryDef, x: number, y: number): Node {
  return {
    id: def.id,
    type: 'library',
    position: { x, y },
    width: 190,
    height: 72,
    data: {
      name: def.name,
      category: def.category,
      metric: def.metric,
    } satisfies LibraryNodeData,
  }
}

function makeTenantNode(tenant: TopologyLayoutData['tenants'][number], x: number, y: number): Node {
  return {
    id: `tenant-${tenant.name}`,
    type: 'tenant',
    position: { x, y },
    width: 180,
    height: 75,
    data: {
      name: tenant.name,
      color: tenant.color,
      status: tenant.status,
      requests24h: tenant.requests24h,
      detections: tenant.detections,
    } satisfies TenantNodeData,
  }
}

function makeConnectorNode(
  tenantName: string,
  connector: TopologyLayoutData['tenants'][number]['connectors'][number],
  x: number,
  y: number
): Node {
  return {
    id: `conn-${tenantName}-${connector.name}`,
    type: 'connector',
    position: { x, y },
    width: 155,
    height: 42,
    data: {
      name: connector.name,
      type: connector.type,
      status: connector.status,
    } satisfies ConnectorNodeData,
  }
}

function makeColumnLabels(labelY: number): Node[] {
  return [
    {
      id: 'label-sources',
      type: 'columnLabel',
      position: { x: COL_SOURCE + 50, y: labelY },
      data: { label: 'Sources' },
      selectable: false,
      draggable: false,
    },
    {
      id: 'label-pipeline',
      type: 'columnLabel',
      position: { x: COL_PIPELINE + 55, y: labelY },
      data: { label: 'Pipeline' },
      selectable: false,
      draggable: false,
    },
    {
      id: 'label-library',
      type: 'columnLabel',
      position: { x: COL_LIBRARY + 35, y: labelY },
      data: { label: 'Threat Library' },
      selectable: false,
      draggable: false,
    },
    {
      id: 'label-tenants',
      type: 'columnLabel',
      position: { x: COL_TENANT + 50, y: labelY },
      data: { label: 'Tenants' },
      selectable: false,
      draggable: false,
    },
    {
      id: 'label-connectors',
      type: 'columnLabel',
      position: { x: COL_CONNECTOR + 30, y: labelY },
      data: { label: 'Connectors' },
      selectable: false,
      draggable: false,
    },
  ]
}

// ── Tenant-specific pipeline ────────────────────────────────────────────────

export function buildPipelineForTenant(
  tenantName: string,
  data: TopologyLayoutData
): { nodes: Node[]; edges: Edge[] } {
  const tenant = data.tenants.find(t => t.name === tenantName)
  if (!tenant) return { nodes: [], edges: [] }

  const libraryDefs = buildLibraryDefs(data.libraryStats)
  const defaultSourceMap: Record<string, string[]> = {}
  const defaultLibraryMap: Record<string, string[]> = {}
  const tenantSourceMap = data.tenantSourceMap ?? defaultSourceMap
  const tenantLibraryMap = data.tenantLibraryMap ?? defaultLibraryMap

  const sourceNames = tenantSourceMap[tenantName] ?? ['SigmaHQ']
  const libraryIds = tenantLibraryMap[tenantName] ?? ['lib-detections']
  const libraries = libraryDefs.filter(l => libraryIds.includes(l.id))

  const nodes: Node[] = []
  const edges: Edge[] = []

  // Spacing constants
  const sourceSpacing = 95
  const libSpacing = 85
  const connSpacing = 65

  // Center everything around the tallest column
  const sourceExtent = (sourceNames.length - 1) * sourceSpacing
  const libExtent = (libraries.length - 1) * libSpacing
  const connExtent = (tenant.connectors.length - 1) * connSpacing
  const maxExtent = Math.max(sourceExtent, libExtent, connExtent)
  const centerY = maxExtent / 2

  // Sources (left)
  const sourceStartY = centerY - sourceExtent / 2
  sourceNames.forEach((name, i) => {
    const source = data.sources.find(s => s.name === name)
    if (!source) return
    nodes.push(makeSourceNode(source, COL_SOURCE, sourceStartY + i * sourceSpacing))
    const countUnit = sourceCountLabel[name] ?? 'rules'
    edges.push({
      id: `e-${name}-pipeline`,
      source: `src-${name}`,
      target: 'platform-pipeline',
      type: 'animated',
      data: {
        label: `${source.ruleCount.toLocaleString()} ${countUnit}`,
        status:
          source.health === 'healthy'
            ? 'healthy'
            : source.health === 'warning'
              ? 'degraded'
              : 'error',
      },
    })
  })

  // Pipeline (center-left)
  nodes.push(makePipelineNode(centerY))

  // Library nodes (center) — fan out from pipeline
  const libStartY = centerY - libExtent / 2
  libraries.forEach((lib, i) => {
    nodes.push(makeLibraryNode(lib, COL_LIBRARY, libStartY + i * libSpacing))
    edges.push({
      id: `e-pipeline-${lib.id}`,
      source: 'platform-pipeline',
      target: lib.id,
      type: 'animated',
      data: { status: 'healthy' },
    })
    // Library -> Tenant
    edges.push({
      id: `e-${lib.id}-${tenant.name}`,
      source: lib.id,
      target: `tenant-${tenant.name}`,
      type: 'animated',
      data: {
        status: tenant.status === 'degraded' ? 'degraded' : 'healthy',
      },
    })
  })

  // Tenant (right of libraries)
  nodes.push(makeTenantNode(tenant, COL_TENANT, centerY - 37))

  // Connectors (far right)
  const connStartY = centerY - connExtent / 2
  tenant.connectors.forEach((c, i) => {
    const connId = `conn-${tenant.name}-${c.name}`
    nodes.push(makeConnectorNode(tenant.name, c, COL_CONNECTOR, connStartY + i * connSpacing))
    edges.push({
      id: `e-${tenant.name}-${c.name}`,
      source: `tenant-${tenant.name}`,
      target: connId,
      type: 'animated',
      data: {
        status:
          c.status === 'connected' ? 'healthy' : c.status === 'degraded' ? 'degraded' : 'error',
      },
    })
  })

  // Column labels
  const labelY = Math.min(sourceStartY, libStartY, centerY - 45, connStartY) - 50
  nodes.push(...makeColumnLabels(labelY))

  return { nodes, edges }
}

// ── Global pipeline (all tenants) ───────────────────────────────────────────

export function buildGlobalPipeline(
  data: Pick<TopologyLayoutData, 'sources' | 'tenants' | 'libraryStats'>
): { nodes: Node[]; edges: Edge[] } {
  const { sources, tenants } = data
  const libraryDefs = buildLibraryDefs(data.libraryStats)
  const nodes: Node[] = []
  const edges: Edge[] = []

  // Sources (left)
  const sourceSpacing = 95
  sources.forEach((s, i) => {
    nodes.push(makeSourceNode(s, COL_SOURCE, i * sourceSpacing))
    edges.push({
      id: `e-${s.name}-pipeline`,
      source: `src-${s.name}`,
      target: 'platform-pipeline',
      type: 'animated',
      data: {
        status: s.health === 'healthy' ? 'healthy' : s.health === 'warning' ? 'degraded' : 'error',
      },
    })
  })

  // Pipeline (center-left)
  const totalSourceHeight = (sources.length - 1) * sourceSpacing
  const centerY = totalSourceHeight / 2
  nodes.push(makePipelineNode(centerY))

  // Library nodes (center) — fan out from pipeline
  const libSpacing = 85
  const libExtent = (libraryDefs.length - 1) * libSpacing
  const libStartY = centerY - libExtent / 2

  libraryDefs.forEach((lib, i) => {
    nodes.push(makeLibraryNode(lib, COL_LIBRARY, libStartY + i * libSpacing))
    edges.push({
      id: `e-pipeline-${lib.id}`,
      source: 'platform-pipeline',
      target: lib.id,
      type: 'animated',
      data: { status: 'healthy' },
    })
  })

  // Tenants (right of libraries) — space based on connector count
  const CONN_H = 55
  const TENANT_MIN_SPACING = 95

  const tenantYPositions: number[] = []
  let currentY = 0
  tenants.forEach((t, i) => {
    if (i === 0) {
      tenantYPositions.push(currentY)
    } else {
      const prevTenant = tenants[i - 1]
      const prevConnectors = prevTenant ? prevTenant.connectors.length : 0
      const currConnectors = t.connectors.length
      const prevBlockHeight = Math.max(prevConnectors, 1) * CONN_H
      const currBlockHeight = Math.max(currConnectors, 1) * CONN_H
      const neededGap = prevBlockHeight / 2 + currBlockHeight / 2 + 15
      currentY += Math.max(neededGap, TENANT_MIN_SPACING)
      tenantYPositions.push(currentY)
    }
  })

  const lastTenantY = tenantYPositions[tenantYPositions.length - 1]
  const tenantTotalHeight = lastTenantY ?? 0
  const tenantOffsetY = centerY - tenantTotalHeight / 2

  tenants.forEach((t, i) => {
    const posY = tenantYPositions[i]
    if (posY === undefined) return
    const tenantY = tenantOffsetY + posY
    nodes.push(makeTenantNode(t, COL_TENANT, tenantY))

    // In global view: Detection Library -> all tenants (main flow line)
    edges.push({
      id: `e-lib-detections-${t.name}`,
      source: 'lib-detections',
      target: `tenant-${t.name}`,
      type: 'animated',
      data: {
        status: t.status === 'degraded' ? 'degraded' : 'healthy',
      },
    })

    // Connectors (far right of each tenant)
    const connTotalHeight = (t.connectors.length - 1) * CONN_H
    const connStartY = tenantY - connTotalHeight / 2 + 10

    t.connectors.forEach((c, j) => {
      const connId = `conn-${t.name}-${c.name}`
      nodes.push(makeConnectorNode(t.name, c, COL_CONNECTOR, connStartY + j * CONN_H))
      edges.push({
        id: `e-${t.name}-${c.name}`,
        source: `tenant-${t.name}`,
        target: connId,
        type: 'animated',
        data: {
          status:
            c.status === 'connected' ? 'healthy' : c.status === 'degraded' ? 'degraded' : 'error',
        },
      })
    })
  })

  // Column labels
  nodes.push(...makeColumnLabels(-50))

  return { nodes, edges }
}
