// @ts-nocheck — route controller converted to .cts; handler req/res typed, full type-check deferred (thin glue over typed services).
import type { Req, Res, Next } from '../types/http';
const express = require('express');
const fs = require('fs');
const path = require('path');
const { requireAuth, requireAdmin } = require('../middleware/requireAuth.cts');

const router = express.Router();

let pkg;
try {
  pkg = require('../package.json');
} catch {
  pkg = { version: '0.1.0' };
}

// Explicit allowlist of allowed files with resolved paths
const ALLOWED_FILES = {
  'FUTURE.md': path.resolve(__dirname, '..', 'FUTURE.md'),
  'DEVELOPMENT_LOG.md': path.resolve(__dirname, '..', 'DEVELOPMENT_LOG.md'),
};

// Priority emoji to label mapping
const PRIORITY_MAP = {
  '🔴': 'CRITICAL',
  '🟠': 'HIGH',
  '🟡': 'MEDIUM',
  '🔵': 'LOW',
  '💭': 'NICE_TO_HAVE',
};

/**
 * Generate a slug from a title: lowercase, hyphens, strip emojis
 */
function slugify(title) {
  return title
    .replace(
      /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{FE00}-\u{FE0F}\u{1F000}-\u{1FAFF}]/gu,
      '',
    ) // strip emojis
    .replace(/[^a-zA-Z0-9]+/g, '-') // non-alphanumeric → hyphens
    .replace(/^-+|-+$/g, '') // trim leading/trailing hyphens
    .toLowerCase();
}

/**
 * Extract effort estimate from implementation notes text.
 * Matches patterns like "Estimated effort: 3-4 hours", "Estimated effort: 8 hours"
 */
function extractEffort(text) {
  if (!text) return null;
  const match = text.match(/Estimated effort:\s*(\d+(?:\s*-\s*\d+)?\s*hours?)/i);
  if (!match) return null;
  // Normalize: "3-4 hours" → "3-4h", "8 hours" → "8h"
  return match[1].replace(/\s*hours?/i, 'h').replace(/\s*/g, '');
}

/**
 * Parse FUTURE.md into structured roadmap items.
 * Filters out completed/strikethrough items and template/meta sections.
 */
function parseFutureMd(content) {
  if (!content) return { items: [], counts: {} };

  const items = [];
  const counts = { critical: 0, high: 0, medium: 0, low: 0, niceToHave: 0 };

  const lines = content.split('\n');
  let skipSection = false;
  let currentSectionLines = [];
  let currentPriorityEmoji = null;
  let currentPriorityLabel = null;
  let currentTitle = null;
  let inItem = false;

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];

    // Skip template/meta sections
    if (/^##\s+How to Use This Document/i.test(line) || /^###\s+Priority Format/i.test(line)) {
      skipSection = true;
      continue;
    }
    // Completed items section
    if (/^##\s+Completed/i.test(line)) {
      skipSection = true;
      continue;
    }
    // Stop skipping at ## or ### headings that aren't skipped sections
    if (skipSection) {
      if (
        /^(?:##|###)\s/.test(line) &&
        !/^(?:##|###)\s+(How to Use|Priority Format|Completed)/i.test(line)
      ) {
        skipSection = false;
        // Don't continue — process this heading line below
      } else {
        continue;
      }
    }

    // Skip table rows (Priority Format table)
    if (/^\|/.test(line)) continue;

    // Strikethrough items: ### ~~Title~~ — PRIORITY
    if (/^###\s+~~/.test(line)) {
      // Save previous item and skip completed/strikethrough items
      if (inItem && currentTitle) {
        _addItem(
          items,
          counts,
          currentPriorityEmoji,
          currentPriorityLabel,
          currentTitle,
          currentSectionLines,
        );
      }
      inItem = false;
      currentTitle = null;
      currentSectionLines = [];
      continue;
    }

    // Priority section headings: ### 🔴 CRITICAL, ### 🟠 HIGH, etc.
    const sectionMatch = line.match(
      /^###\s+(🔴|🟠|🟡|🔵|💭)\s+(CRITICAL|HIGH|MEDIUM|LOW|NICE TO HAVE|NICE_TO_HAVE)\s*$/,
    );
    if (sectionMatch) {
      // Save previous item
      if (inItem && currentTitle) {
        _addItem(
          items,
          counts,
          currentPriorityEmoji,
          currentPriorityLabel,
          currentTitle,
          currentSectionLines,
        );
      }
      currentPriorityEmoji = sectionMatch[1];
      currentPriorityLabel = sectionMatch[2];
      inItem = false;
      currentTitle = null;
      currentSectionLines = [];
      continue;
    }

    // Item headings: ### 🔴 Title — CRITICAL or ### Title — HIGH etc.
    const headingMatch = line.match(
      /^###\s+(🔴|🟠|🟡|🔵|💭)\s+(.+?)\s*—\s*(CRITICAL|HIGH|MEDIUM|LOW|NICE TO HAVE|NICE_TO_HAVE|MEH)\s*$/,
    );
    const headingMatchNoEmoji = line.match(
      /^###\s+(.+?)\s*—\s*(CRITICAL|HIGH|MEDIUM|LOW|NICE TO HAVE|NICE_TO_HAVE|MEH)\s*$/,
    );

    if (headingMatch) {
      if (inItem && currentTitle) {
        _addItem(
          items,
          counts,
          currentPriorityEmoji,
          currentPriorityLabel,
          currentTitle,
          currentSectionLines,
        );
      }
      currentPriorityEmoji = headingMatch[1];
      currentPriorityLabel = headingMatch[3];
      currentTitle = headingMatch[2].trim();
      currentSectionLines = [];
      inItem = true;
      continue;
    }

    if (!headingMatch && headingMatchNoEmoji) {
      if (inItem && currentTitle) {
        _addItem(
          items,
          counts,
          currentPriorityEmoji,
          currentPriorityLabel,
          currentTitle,
          currentSectionLines,
        );
      }
      currentPriorityEmoji = currentPriorityEmoji || null; // inherit from section
      currentPriorityLabel = headingMatchNoEmoji[2];
      currentTitle = headingMatchNoEmoji[1].trim();
      currentSectionLines = [];
      inItem = true;
      continue;
    }

    // Also handle items with emoji but no trailing priority: ### 🔴 Title
    const headingEmojiOnly = line.match(/^###\s+(🔴|🟠|🟡|🔵|💭)\s+(.+?)\s*$/);
    if (headingEmojiOnly && !headingMatch) {
      if (inItem && currentTitle) {
        _addItem(
          items,
          counts,
          currentPriorityEmoji,
          currentPriorityLabel,
          currentTitle,
          currentSectionLines,
        );
      }
      currentPriorityEmoji = headingEmojiOnly[1];
      // Use section-level priority if available
      currentPriorityLabel = currentPriorityLabel || PRIORITY_MAP[headingEmojiOnly[1]] || 'MEDIUM';
      currentTitle = headingEmojiOnly[2].trim();
      currentSectionLines = [];
      inItem = true;
      continue;
    }

    // Generic ### headings without emoji or priority label (items in a section context)
    if (/^###\s+/.test(line) && !headingMatch && !headingMatchNoEmoji && !headingEmojiOnly) {
      // Plain ### heading within a known section
      if (currentPriorityLabel) {
        // Save previous item
        if (inItem && currentTitle) {
          _addItem(
            items,
            counts,
            currentPriorityEmoji,
            currentPriorityLabel,
            currentTitle,
            currentSectionLines,
          );
        }
        currentTitle = line.replace(/^###\s+/, '').trim();
        currentSectionLines = [];
        inItem = true;
        continue;
      }
    }

    // ## Pending Recommendations heading — skip
    if (/^##\s+Pending Recommendations/.test(line)) {
      if (inItem && currentTitle) {
        _addItem(
          items,
          counts,
          currentPriorityEmoji,
          currentPriorityLabel,
          currentTitle,
          currentSectionLines,
        );
      }
      inItem = false;
      currentTitle = null;
      currentSectionLines = [];
      continue;
    }

    // Collect body lines for current item
    if (inItem) {
      currentSectionLines.push(line);
    }
  }

  // Save last item
  if (inItem && currentTitle) {
    _addItem(
      items,
      counts,
      currentPriorityEmoji,
      currentPriorityLabel,
      currentTitle,
      currentSectionLines,
    );
  }

  return { items, counts, version: pkg.version };
}

/**
 * Add a parsed item to the items array and update counts.
 */
function _addItem(items, counts, emoji, label, title, bodyLines) {
  const body = bodyLines.join('\n');
  const description = _extractField(body, 'Description');
  const rationale = _extractField(body, 'Rationale');
  const implementationNotes = _extractField(body, 'Implementation Notes');
  const effort = extractEffort(implementationNotes);

  // Extract Added and AddedBy metadata
  const addedMatch = body.match(/\*\*Added:\*\*\s*(\d{4}-\d{2}-\d{2})(?:\s+by\s+(.+))?/);
  const added = addedMatch ? addedMatch[1] : null;
  const addedBy = addedMatch ? addedMatch[2] || null : null;

  // Determine status — if not specified, default to PENDING
  const statusMatch = body.match(/\*\*Status:\*\*\s*(.+)/);
  const status = statusMatch ? statusMatch[1].trim().toUpperCase() : 'PENDING';

  // Map priority label to count key
  const countKey =
    {
      CRITICAL: 'critical',
      HIGH: 'high',
      MEDIUM: 'medium',
      LOW: 'low',
      'NICE TO HAVE': 'niceToHave',
      NICE_TO_HAVE: 'niceToHave',
      MEH: 'niceToHave',
    }[label] || 'medium';
  counts[countKey]++;

  items.push({
    id: slugify(title),
    priority: emoji || '',
    priorityLabel: label,
    title,
    description,
    rationale,
    implementationNotes,
    effort,
    added,
    addedBy,
    status,
  });
}

/**
 * Extract a named field from markdown body text.
 * Looks for **Field Name:** and captures everything until the next ** field or ### heading or end.
 */
function _extractField(body, fieldName) {
  // Match **FieldName:** followed by content until next ** or ### heading
  const regex = new RegExp(
    `\\*\\*${fieldName}:\\*\\*\\s*\n([\\s\\S]*?)(?=\\n\\*\\*[^*]|\\n###|$)`,
    'i',
  );
  const match = body.match(regex);
  if (!match) return null;
  return match[1].trim();
}

/**
 * Parse DEVELOPMENT_LOG.md into structured log entries.
 * Returns entries sorted by date descending.
 */
function parseDevLogMd(content) {
  if (!content) return [];

  const entries = [];
  // Split on version headings: ### v0.24.4 - Title
  const versionRegex = /^###\s+(v[\d.]+(?:-[\w]+)?)\s+-\s+(.+)$/gm;
  const splits = [];
  let match;
  while ((match = versionRegex.exec(content)) !== null) {
    splits.push({
      version: match[1],
      title: match[2].trim(),
      index: match.index,
    });
  }

  for (let i = 0; i < splits.length; i++) {
    const start = splits[i].index;
    const end = i + 1 < splits.length ? splits[i + 1].index : content.length;
    const block = content.substring(start, end);
    const entry = _parseDevLogEntry(block, splits[i].version, splits[i].title);
    if (entry) entries.push(entry);
  }

  // Sort by date descending
  entries.sort((a, b) => {
    const dateA = a.date ? new Date(a.date) : new Date(0);
    const dateB = b.date ? new Date(b.date) : new Date(0);
    return dateB - dateA;
  });

  return entries;
}

/**
 * Parse a single dev log entry block.
 */
function _parseDevLogEntry(block, version, title) {
  // Status
  const statusMatch = block.match(/\*\*Status:\*\*\s*(.+)/);
  const status = statusMatch ? statusMatch[1].trim() : null;

  // Date
  const dateMatch = block.match(/\*\*Date:\*\*\s*(\d{4}-\d{2}-\d{2})/);
  const date = dateMatch ? dateMatch[1] : null;

  // Priority
  const priorityMatch = block.match(/\*\*Priority:\*\*\s*(.+)/);
  const priority = priorityMatch ? priorityMatch[1].trim() : null;

  // Agents table
  const agents = [];
  const agentTableRegex = /\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|/g;
  let inAgentTable = false;
  const blockLines = block.split('\n');
  for (const line of blockLines) {
    if (/^\|\s*Agent\s*\|/i.test(line)) {
      inAgentTable = true;
      continue;
    }
    if (/^\|\s*[-:]+\s*\|/.test(line)) continue; // separator row
    if (inAgentTable && /^\|/.test(line)) {
      const row = line.match(/\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|/);
      if (row) {
        agents.push({
          name: row[1].trim(),
          status: row[2].trim(),
          time: row[3].trim(),
          notes: row[4].trim(),
        });
      }
    } else if (inAgentTable && !/^\|/.test(line) && line.trim() !== '') {
      inAgentTable = false;
    }
  }

  // Files modified
  const filesMatch = block.match(/\*\*Files modified:\*\*\s*(.+)/);
  const filesModified = filesMatch
    ? filesMatch[1]
        .split(',')
        .map((f) => f.trim().replace(/^`|`$/g, ''))
        .filter(Boolean)
    : [];

  // Work completed (checklist items)
  const workCompleted = [];
  const workMatch = block.match(/\*\*Work Completed:\*\*\n([\s\S]*?)(?=\n---|\n###|$)/);
  if (workMatch) {
    const items = workMatch[1].match(/- \[[ x]\] .+/g);
    if (items) {
      workCompleted.push(...items.map((item) => item.replace(/^- \[[ x]\]\s*/, '').trim()));
    }
  }

  return {
    version,
    title,
    date,
    status,
    priority,
    agents,
    filesModified,
    workCompleted,
  };
}

/**
 * Redact sensitive information from file content
 * @param {string} content - The content to redact
 * @returns {string} - The redacted content
 */
function redactSensitiveContent(content) {
  if (!content) return content;

  return (
    content
      // Redact internal IPs
      .replace(/\b192\.168\.[0-9]{1,3}\.[0-9]{1,3}\b/g, '[REDACTED]')
      .replace(/\b10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\b/g, '[REDACTED]')
      .replace(/\b172\.(1[6-9]|2[0-9]|3[0-1])\.[0-9]{1,3}\.[0-9]{1,3}\b/g, '[REDACTED]')
      // Redact passwords, api_keys, secrets
      .replace(/(password|api_key|secret)\s*=\s*[^\\\s]+/gi, '$1=[REDACTED]')
      // Redact file paths (Unix-style: /home/, /etc/, /var/, /tmp/, /usr/, /opt/)
      .replace(/\/(?:home|etc|var|tmp|usr|opt)\/[^\s"',;)]+/gi, '[REDACTED]')
      // Redact Windows-style paths
      .replace(/[A-Z]:\\(?:Users|Windows|Program Files)[\\\/][^\s"',;)]+/gi, '[REDACTED]')
      // Redact connection strings
      .replace(/(?:mongodb|postgres|mysql|redis|amqp):\/\/[^\s"']+/gi, '[REDACTED]')
      // Redact env var values (KEY=value patterns where key contains secret/pass/key/token)
      .replace(
        /([A-Z_]*(?:SECRET|KEY|TOKEN|PASS|PASSWORD|CREDENTIAL|AUTH)[A-Z_]*)\s*=\s*[^\s"']+/gi,
        '$1=[REDACTED]',
      )
      // Redact internal URLs
      .replace(
        /https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::\d+)?[^\s"']*/gi,
        '[REDACTED_URL]',
      )
      // Redact lines with security-sensitive patterns (CVE IDs, exploit details, attack vectors)
      .replace(/\bCVE-\d{4}-\d+\b/gi, '[REDACTED]')
      .replace(
        /\b(?:sql\s*injection|xss|csrf|csrf\s*token|race\s*condition|buffer\s*overflow|privilege\s*escalation)\b[^.]*\./gi,
        '[REDACTED_SECURITY_CONTENT].',
      )
      .replace(/\bpassword\s*=\s*['"][^'"\s]+['"]/gi, 'password=[REDACTED]')
  );
}

// ── Forgejo issues cache ──────────────────────────────────────────────────────
const FORGEJO_BASE = 'https://dream.scheller.ltd/api/v1/repos/null/BillTracker';
let _forgejoCache = null;
let _forgejoCacheTs = 0;
const FORGEJO_TTL_MS = 5 * 60 * 1000;

async function fetchForgejoIssues() {
  const res = await fetch(`${FORGEJO_BASE}/issues?type=issues&state=open&limit=50&page=1`, {
    headers: { Accept: 'application/json' },
    signal: AbortSignal.timeout(10000),
  });
  if (!res.ok) throw new Error(`Forgejo API returned ${res.status}`);
  const issues = await res.json();
  if (!Array.isArray(issues)) throw new Error('Unexpected Forgejo response shape');
  return issues;
}

// Admin-only endpoint to serve FUTURE.md and DEVELOPMENT_LOG.md content (raw markdown, backward compat)
router.get('/', requireAuth, requireAdmin, (req: Req, res: Res) => {
  try {
    // Read both files directly from the allowlist
    const futureContent = fs.readFileSync(ALLOWED_FILES['FUTURE.md'], 'utf-8');
    const devLogContent = fs.readFileSync(ALLOWED_FILES['DEVELOPMENT_LOG.md'], 'utf-8');

    // Redact sensitive information
    const sanitizedFutureContent = redactSensitiveContent(futureContent);
    const sanitizedDevLogContent = redactSensitiveContent(devLogContent);

    res.json({
      version: pkg.version,
      future: sanitizedFutureContent,
      developmentLog: sanitizedDevLogContent,
    });
  } catch (err) {
    // Generic error message to prevent path disclosure
    console.error('[aboutAdmin] Error reading files');
    res.status(500).json({
      error: 'Failed to read project documentation files',
      code: 'FILE_READ_ERROR',
    });
  }
});

// Admin-only endpoint: open issues from Forgejo (5-min cache, ?refresh=1 to bypass)
router.get('/roadmap', requireAuth, requireAdmin, async (req: Req, res: Res) => {
  const now = Date.now();
  const refresh = req.query.refresh === '1';
  try {
    if (!refresh && _forgejoCache && now - _forgejoCacheTs < FORGEJO_TTL_MS) {
      return res.json(_forgejoCache);
    }
    const issues = await fetchForgejoIssues();
    _forgejoCache = { issues, fetchedAt: new Date().toISOString() };
    _forgejoCacheTs = now;
    res.json(_forgejoCache);
  } catch (err) {
    console.error('[aboutAdmin] Forgejo issues error:', err.message);
    if (_forgejoCache) return res.json({ ..._forgejoCache, stale: true });
    res
      .status(502)
      .json({ error: 'Failed to fetch issues from repository', code: 'FORGEJO_ERROR' });
  }
});

// Admin-only endpoint: parsed dev log entries from DEVELOPMENT_LOG.md
router.get('/dev-log', requireAuth, requireAdmin, (req: Req, res: Res) => {
  try {
    const devLogContent = fs.readFileSync(ALLOWED_FILES['DEVELOPMENT_LOG.md'], 'utf-8');
    const sanitized = redactSensitiveContent(devLogContent);
    const entries = parseDevLogMd(sanitized);
    res.json({ entries, version: pkg.version });
  } catch (err) {
    console.error('[aboutAdmin] Error reading DEVELOPMENT_LOG.md for dev-log');
    res.status(500).json({
      error: 'Failed to read dev log data',
      code: 'FILE_READ_ERROR',
    });
  }
});

const { checkForUpdates } = require('../services/updateCheckService.cts');

// POST /api/about-admin/check-updates — force a fresh update check, bypassing cache
router.post('/check-updates', requireAuth, requireAdmin, async (req: Req, res: Res) => {
  try {
    const result = await checkForUpdates(true);
    res.json(result);
  } catch (err) {
    res.status(500).json({ error: 'Update check failed' });
  }
});

// QA-B16-01: opt-out control for the external version check.
const { getSetting, setSetting } = require('../db/database.cts');

// GET /api/about-admin/update-check-setting — is the external version check enabled?
router.get('/update-check-setting', requireAuth, requireAdmin, (req: Req, res: Res) => {
  res.json({ enabled: getSetting('update_check_enabled') !== 'false' });
});

// PUT /api/about-admin/update-check-setting — enable/disable the external version check
router.put('/update-check-setting', requireAuth, requireAdmin, (req: Req, res: Res) => {
  const { enabled } = req.body || {};
  if (typeof enabled !== 'boolean') {
    return res.status(400).json({ error: 'enabled must be a boolean' });
  }
  setSetting('update_check_enabled', enabled ? 'true' : 'false');
  res.json({ enabled });
});

module.exports = router;
