import { Router } from 'express';
import fs from 'fs';
import path from 'path';
import multer from 'multer';
import { db, save, uid, UPLOADS_DIR, getCrawlOffset, saveCrawlOffset } from '../store';
import { requireAuth, AuthedRequest } from '../auth';
import { countPages, generatePlaceholderPdf, pageCountFromUrl } from '../pdf';
import { crawlForPdfs } from '../crawler';
import { analyzePdf, analyzeStructure, SUGGESTIONS } from '../analysis';
import { extractStructTree } from '../pdfix';
import { PdfDocument } from '../types';

const router = Router();

const storage = multer.diskStorage({
  destination: (_req, _file, cb) => cb(null, UPLOADS_DIR),
  filename: (_req, file, cb) => {
    const safe = file.originalname.replace(/[^a-zA-Z0-9._-]/g, '_');
    cb(null, `${Date.now()}-${safe}`);
  },
});

const upload = multer({
  storage,
  limits: { fileSize: 50 * 1024 * 1024 }, // Up to 50 MB, per the design
  fileFilter: (_req, file, cb) => {
    if (file.mimetype === 'application/pdf' || file.originalname.toLowerCase().endsWith('.pdf')) cb(null, true);
    else cb(new Error('PDF format only'));
  },
});

const STATUS_FILTER_MAP: Record<string, PdfDocument['status']> = {
  scanning: 'scanning',
  pending: 'pending_scan',
  ready: 'ready',
  processing: 'processing',
  remediated: 'remediated',
  error: 'error',
};

/**
 * `type` selects which dashboard tab this list is for — each tab has its own
 * base filter, so the frontend doesn't have to reimplement it client-side:
 *   - 'upload': files + manually-added URLs still in the active queue
 *     (crawler-discovered docs and already-remediated docs live on their own
 *     tabs and never appear here).
 *   - 'scan': Website Scan tab — crawler-discovered docs only, optionally
 *     scoped to one of the user's domains (`domain`) and/or `status`.
 *   - 'remediated': completed docs only, optionally filtered by `source`.
 * With no `type` (legacy callers, e.g. the onboarding trial page which has
 * no tabs/pagination concept), every document is returned unpaginated.
 */
router.get('/', requireAuth, (req: AuthedRequest, res) => {
  const userId = req.user!.id;
  const type = String(req.query.type ?? '');
  let list = db.documents.filter((d) => d.userId === userId);

  if (type === 'upload') {
    list = list.filter((d) => d.source !== 'web' && ['ready', 'pending_scan', 'scanning', 'processing'].includes(d.status));
  } else if (type === 'scan') {
    list = list.filter((d) => d.source === 'web');
    const domain = String(req.query.domain ?? '').trim();
    if (domain) list = list.filter((d) => d.domain === domain);
    const status = String(req.query.status ?? 'all');
    if (status !== 'all' && STATUS_FILTER_MAP[status]) list = list.filter((d) => d.status === STATUS_FILTER_MAP[status]);
  } else if (type === 'remediated') {
    list = list.filter((d) => d.status === 'remediated');
    const source = String(req.query.source ?? 'all');
    if (source !== 'all') list = list.filter((d) => d.source === source);
  }

  const search = String(req.query.search ?? '').trim().toLowerCase();
  if (search) {
    list = list.filter((d) =>
      d.name.toLowerCase().includes(search) ||
      (d.sourceUrl ?? '').toLowerCase().includes(search) ||
      (d.remediatedName ?? '').toLowerCase().includes(search)
    );
  }

  // Latest first.
  list = [...list].sort((a, b) => b.createdAt.localeCompare(a.createdAt));

  if (!type) {
    res.json({ documents: list });
    return;
  }

  const page = Math.max(1, parseInt(String(req.query.page ?? '1'), 10) || 1);
  const perPage = Math.min(100, Math.max(1, parseInt(String(req.query.perPage ?? '10'), 10) || 10));
  const total = list.length;
  const start = (page - 1) * perPage;
  res.json({ documents: list.slice(start, start + perPage), total, page, perPage });
});

router.post('/upload', requireAuth, upload.single('file'), async (req: AuthedRequest, res) => {
  const file = req.file;
  if (!file) {
    res.status(400).json({ error: 'Please choose a PDF file to upload.' });
    return;
  }
  try {
    const pages = await countPages(file.path);
    const doc: PdfDocument = {
      id: uid(),
      userId: req.user!.id,
      name: file.originalname,
      source: 'file',
      status: 'ready',
      pages,
      sizeBytes: file.size,
      storagePath: file.path,
      createdAt: new Date().toISOString(),
    };
    db.documents.push(doc);
    save();
    res.json({ document: doc });
  } catch {
    fs.unlinkSync(file.path);
    res.status(400).json({ error: 'This PDF could not be read. Encrypted files are not supported.' });
  }
});

router.post('/url', requireAuth, (req: AuthedRequest, res) => {
  const { url } = req.body || {};
  const value = String(url || '').trim();
  if (!/^https?:\/\/.+/i.test(value)) {
    res.status(400).json({ error: 'Please enter a valid PDF URL (https://...).' });
    return;
  }
  const name = decodeURIComponent(value.split('/').pop() || 'document.pdf').split('?')[0] || 'document.pdf';
  const doc: PdfDocument = {
    id: uid(),
    userId: req.user!.id,
    name: name.toLowerCase().endsWith('.pdf') ? name : `${name}.pdf`,
    source: 'url',
    sourceUrl: value,
    status: 'pending_scan',
    pages: 0,
    sizeBytes: 0,
    createdAt: new Date().toISOString(),
  };
  db.documents.push(doc);
  save();
  res.json({ document: doc });
});

/**
 * Crawl one of the user's website domains and register every discovered PDF
 * as a pending-scan document (already-known URLs are skipped).
 */
router.post('/crawl', requireAuth, async (req: AuthedRequest, res) => {
  const user = req.user!;
  const { domain } = req.body || {};
  const clean = String(domain || '').trim().replace(/^https?:\/\//, '').replace(/\/.*$/, '');
  if (!clean || !user.domains.includes(clean)) {
    res.status(400).json({ error: 'Please select one of your website domains to crawl.' });
    return;
  }

  const startOffset = await getCrawlOffset(user.id, clean);
  const result = await crawlForPdfs(clean, startOffset);
  if (result.error) {
    res.status(502).json({ error: result.error });
    return;
  }
  await saveCrawlOffset(user.id, clean, result.nextOffset);

  const existing = new Set(
    db.documents.filter((d) => d.userId === user.id && d.sourceUrl).map((d) => d.sourceUrl as string)
  );
  const added: PdfDocument[] = [];
  for (const url of result.pdfUrls) {
    if (existing.has(url)) continue;
    const name = decodeURIComponent(url.split('/').pop() || 'document.pdf').split('?')[0] || 'document.pdf';
    const doc: PdfDocument = {
      id: uid(),
      userId: user.id,
      name: name.toLowerCase().endsWith('.pdf') ? name : `${name}.pdf`,
      source: 'web',
      sourceUrl: url,
      domain: clean,
      status: 'pending_scan',
      pages: 0,
      sizeBytes: 0,
      createdAt: new Date().toISOString(),
    };
    db.documents.push(doc);
    added.push(doc);
  }
  if (added.length) save();

  res.json({
    domain: clean,
    pagesCrawled: result.pagesCrawled,
    sitemapUrlsFound: result.sitemapUrlsFound,
    sitemapOffset: result.sitemapOffset,
    sitemapNextOffset: result.nextOffset,
    truncated: result.truncated,
    found: result.pdfUrls.length,
    added: added.length,
    documents: added,
  });
});

/** Scan a URL document: download the PDF (or generate a demo placeholder) and count pages. */
router.post('/:id/scan', requireAuth, async (req: AuthedRequest, res) => {
  const doc = db.documents.find((d) => d.id === req.params.id && d.userId === req.user!.id);
  if (!doc || (doc.source !== 'url' && doc.source !== 'web')) {
    res.status(404).json({ error: 'Document not found.' });
    return;
  }
  doc.status = 'scanning';
  save();

  const finish = (pages: number, filePath: string, size: number) => {
    doc.pages = pages;
    doc.sizeBytes = size;
    doc.storagePath = filePath;
    doc.status = 'ready';
    save();
    res.json({ document: doc });
  };

  const target = path.join(UPLOADS_DIR, `${Date.now()}-${doc.name.replace(/[^a-zA-Z0-9._-]/g, '_')}`);
  try {
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), 12000);
    const response = await fetch(doc.sourceUrl!, { signal: controller.signal });
    clearTimeout(timer);
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    const buffer = Buffer.from(await response.arrayBuffer());
    fs.writeFileSync(target, buffer);
    const pages = await countPages(target);
    finish(pages, target, buffer.length);
  } catch {
    // Offline / unreachable URL: generate a placeholder so the demo flow still works.
    try {
      const pages = pageCountFromUrl(doc.sourceUrl!);
      await generatePlaceholderPdf(doc.name, pages, target);
      finish(pages, target, fs.statSync(target).size);
    } catch {
      doc.status = 'error';
      save();
      res.status(502).json({ error: 'Could not fetch this URL.', document: doc });
    }
  }
});

router.delete('/:id', requireAuth, (req: AuthedRequest, res) => {
  const idx = db.documents.findIndex((d) => d.id === req.params.id && d.userId === req.user!.id);
  if (idx === -1) {
    res.status(404).json({ error: 'Document not found.' });
    return;
  }
  const [doc] = db.documents.splice(idx, 1);
  for (const p of [doc.storagePath, doc.remediatedPath]) {
    if (p && fs.existsSync(p)) {
      try { fs.unlinkSync(p); } catch { /* best effort */ }
    }
  }
  save();
  res.json({ ok: true });
});

/**
 * AI Suggestions: run accessibility checks on the document and pair every
 * finding with a plain-English explanation + before/after fix example.
 * Analyses the remediated file when available, otherwise the original —
 * `analyzed` in the response says which.
 */
router.get('/:id/suggestions', requireAuth, async (req: AuthedRequest, res) => {
  const doc = db.documents.find((d) => d.id === req.params.id && d.userId === req.user!.id);
  if (!doc) {
    res.status(404).json({ error: 'Document not found.' });
    return;
  }
  const remediated = Boolean(doc.remediatedPath && fs.existsSync(doc.remediatedPath));
  const target = remediated ? doc.remediatedPath! : doc.storagePath;
  if (!target || !fs.existsSync(target)) {
    res.status(409).json({ error: 'This document has no file to analyse yet — scan or upload it first.' });
    return;
  }
  try {
    let checks = await analyzePdf(target);

    // Deep, content-scaled findings from the tag structure (per figure /
    // heading / table). When available, they replace the crude doc-level
    // figure-alt heuristic.
    const tree = await extractStructTree(target);
    if (tree) {
      const structChecks = analyzeStructure(tree);
      if (structChecks.length > 0) {
        checks = checks.filter((c) => c.code !== 'FIGURE_ALT_MISSING').concat(structChecks);
      }
    }

    const items = checks.map((c) => {
      const kb = SUGGESTIONS[c.code];
      return {
        code: c.code,
        status: c.status,
        detail: c.detail,
        title: (c.status === 'passed' ? kb?.titleOk : kb?.title) ?? c.code,
        plain: kb?.plain ?? '',
        why: kb?.why ?? '',
        fix: kb?.fix ?? '',
        example: kb?.example ?? { before: '', after: '' },
        // On an un-remediated file, a failed auto-fixable check will be
        // resolved by running AI Remediation — the popup labels it that way.
        willAutoFix: !remediated && c.status === 'failed' && Boolean(kb?.autoFix),
      };
    });
    res.json({
      analyzed: remediated ? 'remediated' : 'original',
      documentName: remediated ? doc.remediatedName ?? doc.name : doc.name,
      pages: doc.pages,
      deepScan: Boolean(tree),
      passed: items.filter((i) => i.status === 'passed').length,
      failed: items.filter((i) => i.status === 'failed').length,
      items,
    });
  } catch {
    res.status(500).json({ error: 'Could not analyse this PDF.' });
  }
});

router.get('/:id/download', requireAuth, (req: AuthedRequest, res) => {
  const doc = db.documents.find((d) => d.id === req.params.id && d.userId === req.user!.id);
  if (!doc || !doc.remediatedPath || !fs.existsSync(doc.remediatedPath)) {
    res.status(404).json({ error: 'Remediated file not available yet.' });
    return;
  }
  res.download(doc.remediatedPath, doc.remediatedName || doc.name);
});

export default router;
