import { NextRequest, NextResponse } from 'next/server'
import { Pool } from 'pg'

// Disable SSL rejection for self-signed certs
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'

const dbUrl = process.env.COMPLIANCE_DATABASE_URL ||
  process.env.DATABASE_URL ||
  'postgresql://breakpilot:breakpilot123@bp-core-postgres:5432/breakpilot_db'

const pool = new Pool({ connectionString: dbUrl })

// handleMeta returns global (filter-independent) counts incl. a ~2s member-join
// facet. It is refetched on every filter change, so cache it briefly.
let metaCache: { at: number; data: unknown } | null = null
const META_TTL_MS = 120_000

// The use-case mapping tables (mc_use_case_mappings, mc_verification,
// mc_regulations, mc_use_case_sync_state) are seeded together per-environment
// and may not exist yet on a fresh/unseeded DB. We probe mc_use_case_mappings as
// the existence sentinel and guard every mapping query so the route degrades to
// empty filters instead of a 500. Short TTL so it picks up the tables once seeded.
// NB: the sentinel assumes the siblings are seeded together — a half-seeded DB
// (mappings present but e.g. mc_regulations missing) would still 500 on those.
let mappingTablesCache: { at: number; present: boolean } | null = null
async function hasMappingTables(): Promise<boolean> {
  if (mappingTablesCache && Date.now() - mappingTablesCache.at < 300_000) {
    return mappingTablesCache.present
  }
  let present = false
  try {
    const r = await pool.query(
      "SELECT to_regclass('compliance.mc_use_case_mappings') IS NOT NULL AS present")
    present = !!r.rows[0]?.present
  } catch { present = false }
  mappingTablesCache = { at: Date.now(), present }
  return present
}

type MCListRow = {
  id: string; control_id: string; title: string; objective: string
  severity: string; category: string; total_controls: number
  phases_covered: string[] | null; created_at: string
  verification_method: string | null; use_cases: string[] | null
  primary_regulation: string | null
}

/**
 * MC API that returns data in the same format as the canonical controls
 * endpoint. This allows the MC page to reuse ControlListView components.
 */
export async function GET(request: NextRequest) {
  try {
    const { searchParams } = new URL(request.url)
    const endpoint = searchParams.get('endpoint') || 'controls'

    switch (endpoint) {
      case 'frameworks':
        return NextResponse.json([])

      case 'controls':
        return handleControls(searchParams)

      case 'controls-count':
        return handleCount(searchParams)

      case 'controls-meta':
        return handleMeta(searchParams)

      case 'control':
        return handleDetail(searchParams)

      default:
        return NextResponse.json({ error: 'unknown' }, { status: 400 })
    }
  } catch (e) {
    return NextResponse.json({ error: String(e) }, { status: 500 })
  }
}

// Shared WHERE builder so list + count stay in lock-step (incl. the
// use_case / verification_method / source_regulation mapping filters).
function buildControlsWhere(params: URLSearchParams, hasMapping: boolean): { where: string; args: unknown[]; idx: number } {
  let where = "WHERE 1=1"
  const args: unknown[] = []
  let idx = 1

  const search = params.get('search') || ''
  if (search) {
    where += ` AND mc.canonical_name ILIKE $${idx}`
    args.push(`%${search}%`)
    idx++
  }

  const severity = params.get('severity') || ''
  if (severity === 'high') { where += ` AND mc.total_controls > 100` }
  else if (severity === 'medium') { where += ` AND mc.total_controls BETWEEN 20 AND 100` }
  else if (severity === 'low') { where += ` AND mc.total_controls < 20` }

  const domain = params.get('domain') || ''
  if (domain) {
    where += ` AND mc.canonical_name LIKE $${idx}`
    args.push(`${domain}%`)
    idx++
  }

  // Mapping-based filters only apply when the mapping tables exist (seeded DB).
  if (hasMapping) {
    const useCase = params.get('use_case') || ''
    const primaryOnly = params.get('primary') === '1'
    if (useCase) {
      where += ` AND EXISTS (SELECT 1 FROM compliance.mc_use_case_mappings m
                   WHERE m.master_control_uuid = mc.id AND m.use_case = $${idx}${primaryOnly ? ' AND m.is_primary' : ''})`
      args.push(useCase)
      idx++
    }

    const verification = params.get('verification_method') || ''
    if (verification === '__none__') {
      where += ` AND NOT EXISTS (SELECT 1 FROM compliance.mc_verification v
                   WHERE v.master_control_uuid = mc.id)`
    } else if (verification) {
      where += ` AND EXISTS (SELECT 1 FROM compliance.mc_verification v
                   WHERE v.master_control_uuid = mc.id AND v.verification_method = $${idx})`
      args.push(verification)
      idx++
    }

    const regulation = params.get('source_regulation') || ''
    if (regulation) {
      where += ` AND EXISTS (SELECT 1 FROM compliance.mc_regulations r
                   WHERE r.master_control_uuid = mc.id AND r.source_regulation = $${idx})`
      args.push(regulation)
      idx++
    }

    const mapped = params.get('mapped') || ''
    if (mapped === 'mapped') {
      where += ` AND EXISTS (SELECT 1 FROM compliance.mc_use_case_mappings m
                   WHERE m.master_control_uuid = mc.id)`
    } else if (mapped === 'unmapped') {
      where += ` AND NOT EXISTS (SELECT 1 FROM compliance.mc_use_case_mappings m
                   WHERE m.master_control_uuid = mc.id)`
    }
  }

  // Member-based filter: an MC matches if ANY of its atomic members has the
  // category. Only category/severity/release_state are populated on the
  // deduplicated members; evidence_type, target_audience and source_citation
  // are 100% NULL there, so those canonical filters cannot apply to MCs
  // without an upstream backfill (wiring them would just return 0).
  const category = params.get('category') || ''
  if (category) {
    where += ` AND EXISTS (SELECT 1 FROM compliance.master_control_members mcm
                 JOIN compliance.canonical_controls cc ON cc.id = mcm.control_uuid
                 WHERE mcm.master_control_uuid = mc.id AND cc.category = $${idx})`
    args.push(category); idx++
  }

  return { where, args, idx }
}

async function handleControls(params: URLSearchParams) {
  const limit = Math.min(parseInt(params.get('limit') || '50'), 200)
  const offset = parseInt(params.get('offset') || '0')
  const sort = params.get('sort') || 'control_id'
  const order = params.get('order') === 'desc' ? 'DESC' : 'ASC'

  const hasMapping = await hasMappingTables()
  const { where, args, idx } = buildControlsWhere(params, hasMapping)

  const sortCol = sort === 'control_id' ? 'mc.master_control_id' :
                  sort === 'created_at' ? 'mc.created_at' :
                  sort === 'source' ? 'mc.canonical_name' : 'mc.master_control_id'

  const mapCols = hasMapping ? `,
           (SELECT v.verification_method FROM compliance.mc_verification v
             WHERE v.master_control_uuid = mc.id) as verification_method,
           (SELECT array_agg(m.use_case ORDER BY m.is_primary DESC, m.use_case)
              FROM compliance.mc_use_case_mappings m
             WHERE m.master_control_uuid = mc.id) as use_cases,
           (SELECT r.source_regulation FROM compliance.mc_regulations r
             WHERE r.master_control_uuid = mc.id AND r.is_primary LIMIT 1) as primary_regulation`
    : `, NULL as verification_method, NULL::text[] as use_cases, NULL as primary_regulation`

  args.push(limit, offset)
  const res = await pool.query(`
    SELECT mc.master_control_id as control_id,
           mc.canonical_name as title,
           'Master Control mit ' || mc.total_controls || ' Atomic Controls' as objective,
           CASE WHEN mc.total_controls > 100 THEN 'high'
                WHEN mc.total_controls > 20 THEN 'medium'
                ELSE 'low' END as severity,
           'master_control' as category,
           mc.total_controls,
           mc.phases_covered,
           mc.id,
           mc.created_at${mapCols}
    FROM compliance.master_controls mc
    ${where}
    ORDER BY ${sortCol} ${order}
    LIMIT $${idx} OFFSET $${idx + 1}
  `, args)

  // Map to canonical control format
  const controls = res.rows.map((r: MCListRow) => ({
    id: r.id,
    control_id: r.control_id,
    title: r.title,
    objective: r.objective,
    severity: r.severity,
    category: r.category,
    release_state: 'active',
    source_citation: r.primary_regulation ? { source: r.primary_regulation } : null,
    verification_method: r.verification_method,
    evidence_type: null,
    target_audience: [],
    use_cases: r.use_cases || [],
    requirements: [],
    test_procedure: [],
    evidence: [],
    open_anchors: [],
    total_controls: r.total_controls,
    phases_covered: r.phases_covered,
    created_at: r.created_at,
    scope: { platforms: [], components: [], data_classes: [] },
    risk_score: null,
    implementation_effort: null,
  }))

  return NextResponse.json(controls)
}

async function handleCount(params: URLSearchParams) {
  const hasMapping = await hasMappingTables()
  const { where, args } = buildControlsWhere(params, hasMapping)
  const res = await pool.query(
    `SELECT count(*) FROM compliance.master_controls mc ${where}`, args
  )
  return NextResponse.json({ total: parseInt(res.rows[0].count) })
}

async function handleMeta(_params: URLSearchParams) {
  if (metaCache && Date.now() - metaCache.at < META_TTL_MS) {
    return NextResponse.json(metaCache.data)
  }
  const res = await pool.query(`
    SELECT count(*) as total,
           count(CASE WHEN total_controls > 100 THEN 1 END) as high_count,
           count(CASE WHEN total_controls BETWEEN 20 AND 100 THEN 1 END) as medium_count,
           count(CASE WHEN total_controls < 20 THEN 1 END) as low_count
    FROM compliance.master_controls
  `)
  const r = res.rows[0]

  // Get top L1 tokens as "domains"
  const domainRes = await pool.query(`
    SELECT split_part(canonical_name, '_', 1) as domain, count(*) as count
    FROM compliance.master_controls
    GROUP BY 1 ORDER BY 2 DESC LIMIT 30
  `)

  // category facet is member-based (those tables always exist); the mapping
  // facets only when the mapping tables are present (seeded DB).
  const hasMapping = await hasMappingTables()
  const catRes = await pool.query(`SELECT cc.category v, count(DISTINCT mcm.master_control_uuid) c
                FROM compliance.master_control_members mcm
                JOIN compliance.canonical_controls cc ON cc.id = mcm.control_uuid
                WHERE cc.category IS NOT NULL GROUP BY 1 ORDER BY 2 DESC`)
  const emptyRows = { rows: [] as Array<Record<string, string>> }
  const [ucRes, vRes, regRes, mappedRes] = hasMapping
    ? await Promise.all([
        pool.query(`SELECT use_case, count(DISTINCT master_control_uuid) c
                    FROM compliance.mc_use_case_mappings GROUP BY 1 ORDER BY 2 DESC`),
        pool.query(`SELECT verification_method, count(*) c
                    FROM compliance.mc_verification GROUP BY 1 ORDER BY 2 DESC`),
        pool.query(`SELECT source_regulation, count(DISTINCT master_control_uuid) c
                    FROM compliance.mc_regulations GROUP BY 1 ORDER BY 2 DESC LIMIT 200`),
        pool.query(`SELECT count(DISTINCT master_control_uuid) c
                    FROM compliance.mc_use_case_mappings`),
      ])
    : [emptyRows, emptyRows, emptyRows, { rows: [{ c: '0' }] }]
  const facet = (rows: Array<{ v: string; c: string }>) =>
    Object.fromEntries(rows.filter(x => x.v).map(x => [x.v, parseInt(x.c)]))

  const total = parseInt(r.total)
  const mappedTotal = parseInt(mappedRes.rows[0].c)

  const payload = {
    total,
    severity_counts: {
      high: parseInt(r.high_count),
      medium: parseInt(r.medium_count),
      low: parseInt(r.low_count),
    },
    domains: domainRes.rows.map((d: { domain: string; count: string }) =>
      ({ domain: d.domain, count: parseInt(d.count) })),
    sources: [],
    no_source_count: 0,
    release_state_counts: { active: total },
    verification_method_counts: Object.fromEntries(
      (vRes.rows as { verification_method: string; c: string }[]).map((x) =>
        [x.verification_method, parseInt(x.c)] as [string, number])),
    category_counts: facet(catRes.rows),
    evidence_type_counts: {},
    use_case_counts: Object.fromEntries(
      ucRes.rows
        .filter((x: { use_case: string | null }) => x.use_case)
        .map((x: { use_case: string; c: string }) => [x.use_case, parseInt(x.c)])),
    regulations: regRes.rows
      .filter((x: { source_regulation: string | null }) => x.source_regulation)
      .map((x: { source_regulation: string; c: string }) =>
        ({ source_regulation: x.source_regulation, count: parseInt(x.c) })),
    mapped_total: mappedTotal,
    unmapped_count: total - mappedTotal,
  }
  metaCache = { at: Date.now(), data: payload }
  return NextResponse.json(payload)
}

async function handleDetail(params: URLSearchParams) {
  const id = params.get('id') || ''
  const res = await pool.query(`
    SELECT mc.id, mc.master_control_id as control_id, mc.canonical_name as title,
           'Master Control mit ' || mc.total_controls || ' Atomic Controls' as objective,
           mc.total_controls, mc.phases_covered, mc.phase_control_count, mc.created_at
    FROM compliance.master_controls mc
    WHERE mc.master_control_id = $1 OR mc.id::text = $1
  `, [id])

  if (res.rows.length === 0) {
    return NextResponse.json({ error: 'not found' }, { status: 404 })
  }

  const mc = res.rows[0]

  // Load members
  const membersRes = await pool.query(`
    SELECT cc.control_id, cc.title, cc.severity, mcm.phase, mcm.action
    FROM compliance.master_control_members mcm
    JOIN compliance.canonical_controls cc ON cc.id = mcm.control_uuid
    WHERE mcm.master_control_uuid = $1
    ORDER BY mcm.phase, cc.control_id
    LIMIT 100
  `, [mc.id])

  // Use-case / verification / regulation mapping (only when the tables exist).
  const mapping: Record<string, any> = (await hasMappingTables())
    ? ((await pool.query(`
    SELECT
      (SELECT json_agg(json_build_object('use_case', m.use_case, 'is_primary', m.is_primary)
                ORDER BY m.is_primary DESC, m.use_case)
         FROM compliance.mc_use_case_mappings m WHERE m.master_control_uuid = $1) as use_cases,
      (SELECT v.verification_method FROM compliance.mc_verification v
         WHERE v.master_control_uuid = $1) as verification_method,
      (SELECT json_agg(json_build_object('source_regulation', r.source_regulation,
                'is_primary', r.is_primary, 'member_count', r.member_count)
                ORDER BY r.is_primary DESC, r.member_count DESC)
         FROM compliance.mc_regulations r WHERE r.master_control_uuid = $1) as regulations
  `, [mc.id])).rows[0] || {})
    : {}
  const regs = mapping.regulations || []
  const primaryReg = regs.find((x: { is_primary: boolean }) => x.is_primary) || regs[0]

  return NextResponse.json({
    id: mc.id,
    control_id: mc.control_id,
    title: mc.title,
    objective: mc.objective,
    severity: mc.total_controls > 100 ? 'high' : mc.total_controls > 20 ? 'medium' : 'low',
    category: 'master_control',
    release_state: 'active',
    total_controls: mc.total_controls,
    phases_covered: mc.phases_covered,
    phase_control_count: mc.phase_control_count,
    members: membersRes.rows,
    requirements: membersRes.rows.map((m: { control_id: string; title: string; phase: string }) =>
      `[${m.phase}] ${m.control_id}: ${m.title}`
    ),
    test_procedure: [],
    evidence: [],
    open_anchors: [],
    target_audience: [],
    verification_method: mapping.verification_method || null,
    use_cases: mapping.use_cases || [],
    regulations: regs,
    source_citation: primaryReg ? { source: primaryReg.source_regulation } : null,
    scope: { platforms: [], components: [], data_classes: [] },
    risk_score: null,
    implementation_effort: null,
    created_at: mc.created_at,
  })
}
