import { Router, Request, Response, NextFunction } from 'express';
import multer from 'multer';
import { rm } from 'fs/promises';
import { extname, basename } from 'path';
import { UserRole } from '@prisma/client';
import { authenticate } from '../../middleware/auth.middleware';
import { requireNonTemp, requireRole } from '../../middleware/rbac.middleware';
import { env } from '../../config/env';
import { CONTENT_ROLES, getUserRoles } from '../../utils/roles';
import { logger } from '../../utils/logger';
import { isServiceOnline } from '../../utils/health-check';
import { cm_docs_operations } from '../../utils/metrics';
import { docsFilesService, PathTraversalError, FileNotFoundError } from './docs-files.service';
import { docsCollabService } from './docs-collab.service';
import { mkdocsConfigService } from './mkdocs-config.service';
import { headerBuilderService } from './header-builder.service';
import { headerConfigSchema } from './header-builder.schemas';
import { docsResetService } from './docs-reset.service';
import { blogService } from './blog.service';
import { newBlogPostSchema, authorsFileSchema } from './blog.schemas';
import { docsAccessService } from './docs-access.service';
import { docsHistoryService } from './docs-history.service';
import { docsTemplatesService } from './docs-templates.service';
import { docsMetadataService } from './docs-metadata.service';

const router = Router();
router.use(authenticate);
router.use(requireNonTemp);

// Removed duplicated isServiceOnline - now using shared utility from utils/health-check.ts

// GET /api/docs/status — check MkDocs and Code Server availability (content editors only)
router.get(
  '/status',
  requireRole(...CONTENT_ROLES),
  async (_req: Request, res: Response, next: NextFunction) => {
    try {
      const [mkdocsOnline, codeServerOnline, siteServerOnline] = await Promise.all([
        isServiceOnline(env.MKDOCS_PREVIEW_URL),
        isServiceOnline(env.CODE_SERVER_URL),
        isServiceOnline(env.MKDOCS_SITE_SERVER_URL),
      ]);

      res.json({
        mkdocs: { online: mkdocsOnline, url: env.MKDOCS_PREVIEW_URL },
        codeServer: { online: codeServerOnline, url: env.CODE_SERVER_URL },
        siteServer: { online: siteServerOnline, url: env.MKDOCS_SITE_SERVER_URL },
      });
    } catch (err) {
      logger.error('Failed to check docs status', err);
      next(err);
    }
  },
);

// GET /api/docs/config — return public-facing port numbers for iframe URLs (content editors only)
router.get(
  '/config',
  requireRole(...CONTENT_ROLES),
  async (_req: Request, res: Response, _next: NextFunction) => {
    res.json({
      codeServerPort: env.CODE_SERVER_PORT,
      mkdocsPort: env.MKDOCS_PORT,
      mkdocsSitePort: env.MKDOCS_SITE_SERVER_PORT,
    });
  },
);

// --- MkDocs Config Endpoints ---

// GET /api/docs/mkdocs-config — read raw mkdocs.yml content (content editors only)
router.get(
  '/mkdocs-config',
  requireRole(...CONTENT_ROLES),
  async (_req: Request, res: Response, next: NextFunction) => {
    try {
      const content = await mkdocsConfigService.readConfig();
      res.json({ content });
    } catch (err) {
      logger.error('Failed to read mkdocs config', err);
      next(err);
    }
  },
);

// PUT /api/docs/mkdocs-config — validate + write mkdocs.yml (SUPER_ADMIN only)
router.put(
  '/mkdocs-config',
  requireRole(...CONTENT_ROLES),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const { content } = req.body as { content?: string };
      if (typeof content !== 'string') {
        res.status(400).json({ error: { message: 'Content string required', code: 'VALIDATION_ERROR' } });
        return;
      }
      await mkdocsConfigService.writeConfig(content);
      res.json({ success: true });
    } catch (err) {
      if ((err as Error).message?.startsWith('Invalid YAML')) {
        res.status(400).json({ error: { message: (err as Error).message, code: 'VALIDATION_ERROR' } });
        return;
      }
      logger.error('Failed to write mkdocs config', err);
      next(err);
    }
  },
);

// POST /api/docs/build — trigger mkdocs build in container
router.post(
  '/build',
  requireRole(...CONTENT_ROLES),
  async (_req: Request, res: Response, next: NextFunction) => {
    try {
      const result = await mkdocsConfigService.triggerBuild();
      res.json(result);
    } catch (err) {
      logger.error('MkDocs build failed', err);
      next(err);
    }
  },
);

// POST /api/docs/reset — reset docs content to baseline
router.post(
  '/reset',
  requireRole('SUPER_ADMIN'),
  async (_req: Request, res: Response, next: NextFunction) => {
    try {
      const result = await docsResetService.resetToBaseline();
      res.json(result);
    } catch (err) {
      logger.error('Docs reset failed', err);
      next(err);
    }
  },
);

// --- Header Builder ---

// GET /api/docs/header-config — read header nav bar config (content editors only)
router.get(
  '/header-config',
  requireRole(...CONTENT_ROLES),
  async (_req: Request, res: Response, next: NextFunction) => {
    try {
      const config = await headerBuilderService.readConfig();
      res.json(config);
    } catch (err) {
      logger.error('Failed to read header config', err);
      next(err);
    }
  },
);

// PUT /api/docs/header-config — save header nav bar config + regenerate template
router.put(
  '/header-config',
  requireRole(...CONTENT_ROLES),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const parsed = headerConfigSchema.safeParse(req.body);
      if (!parsed.success) {
        res.status(400).json({
          error: { message: 'Invalid header config', code: 'VALIDATION_ERROR', details: parsed.error.flatten().fieldErrors },
        });
        return;
      }
      await headerBuilderService.writeConfig(parsed.data);
      // Invalidate docs file tree cache so the new main.html shows up
      await docsFilesService.invalidateTreeCache();
      res.json({ success: true });
    } catch (err) {
      logger.error('Failed to save header config', err);
      next(err);
    }
  },
);

// --- File Upload ---

const ALLOWED_UPLOAD_EXTENSIONS = new Set([
  '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico',
  '.pdf', '.zip',
]);

const upload = multer({
  storage: multer.diskStorage({}), // temp dir
  limits: { fileSize: 20 * 1024 * 1024 }, // 20MB
  fileFilter: (_req, file, cb) => {
    const ext = extname(file.originalname).toLowerCase();
    if (ALLOWED_UPLOAD_EXTENSIONS.has(ext)) {
      cb(null, true);
    } else {
      cb(new Error(`File type not allowed: ${ext}`));
    }
  },
});

// POST /api/docs/upload — upload binary file (image, pdf, etc.)
router.post(
  '/upload',
  requireRole(...CONTENT_ROLES),
  upload.single('file'),
  async (req: Request, res: Response, next: NextFunction) => {
    const tempPath = req.file?.path;
    try {
      cm_docs_operations.inc({ operation: 'upload' });
      if (!req.file) {
        res.status(400).json({ error: { message: 'No file provided', code: 'VALIDATION_ERROR' } });
        return;
      }

      const targetDir = (req.body as { path?: string }).path || '';
      const fileName = basename(req.file.originalname).replace(/[^a-zA-Z0-9._-]/g, '_');
      const relativePath = targetDir ? `${targetDir}/${fileName}` : fileName;

      await docsFilesService.uploadFile(relativePath, req.file.path);

      // Clean up temp file
      try { await rm(req.file.path); } catch { /* ignore */ }

      res.json({ success: true, path: relativePath });
    } catch (err) {
      // Clean up temp file on error
      if (tempPath) { try { await rm(tempPath); } catch { /* ignore */ } }
      handleFileError(err, res, next);
    }
  },
);

// --- File Management Endpoints ---

// GET /api/docs/files — list file tree (content editors only)
router.get(
  '/files',
  requireRole(...CONTENT_ROLES),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      cm_docs_operations.inc({ operation: 'list' });
      if (req.query['force'] === 'true') {
        await docsFilesService.invalidateTreeCache();
      }
      const tree = await docsFilesService.listTree();
      res.json(tree);
    } catch (err) {
      logger.error('Failed to list docs files', err);
      next(err);
    }
  },
);

// GET /api/docs/files/search — search files by name/path (content editors only)
router.get(
  '/files/search',
  requireRole(...CONTENT_ROLES),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const search = String(req.query['search'] ?? req.query['q'] ?? '').trim();
      if (!search) {
        res.json({ files: [] });
        return;
      }
      const limit = Math.min(Math.max(Number(req.query['limit']) || 5, 1), 20);
      const files = await docsFilesService.searchFiles(search, limit);
      res.json({ files });
    } catch (err) {
      logger.error('Failed to search docs files', err);
      next(err);
    }
  },
);

// GET /api/docs/files/search-content — search within file contents
router.get(
  '/files/search-content',
  requireRole(...CONTENT_ROLES),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const query = String(req.query['q'] ?? '').trim();
      if (!query || query.length < 2) {
        res.json({ results: [] });
        return;
      }
      const limit = Math.min(Math.max(Number(req.query['limit']) || 10, 1), 30);
      const results = await docsFilesService.searchContent(query, limit);
      res.json({ results });
    } catch (err) {
      logger.error('Failed to search docs content', err);
      next(err);
    }
  },
);

// POST /api/docs/files/rename — rename/move file
router.post(
  '/files/rename',
  requireRole(...CONTENT_ROLES),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      cm_docs_operations.inc({ operation: 'rename' });
      const { from, to } = req.body as { from?: string; to?: string };
      if (!from || !to) {
        res.status(400).json({ error: { message: 'Both "from" and "to" paths are required', code: 'VALIDATION_ERROR' } });
        return;
      }

      // Check access on both source and destination
      const userRoles = getUserRoles(req.user!);
      const canEditFrom = await docsAccessService.canUserEdit(req.user!.id, userRoles, from);
      if (!canEditFrom) {
        res.status(403).json({ error: { message: 'You do not have edit access to this document', code: 'DOC_ACCESS_DENIED' } });
        return;
      }

      await docsFilesService.renameFile(from, to);
      // Cascade rename for access policies and share links
      docsAccessService.cascadeRename(from, to).catch(() => {});
      // Invalidate old path's collaboration state
      docsCollabService.invalidateDocument(from).catch(() => {});
      res.json({ success: true });
    } catch (err) {
      handleFileError(err, res, next);
    }
  },
);

// GET /api/docs/files/* — read file content (content editors only)
router.get(
  '/files/*',
  requireRole(...CONTENT_ROLES),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      cm_docs_operations.inc({ operation: 'read' });
      const filePath = extractWildcardPath(req);
      if (!filePath) {
        res.status(400).json({ error: { message: 'File path required', code: 'VALIDATION_ERROR' } });
        return;
      }
      const content = await docsFilesService.readFileContent(filePath);
      res.json({ path: filePath, content });
    } catch (err) {
      handleFileError(err, res, next);
    }
  },
);

// PUT /api/docs/files/* — write/update file content
router.put(
  '/files/*',
  requireRole(...CONTENT_ROLES),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      cm_docs_operations.inc({ operation: 'write' });
      const filePath = extractWildcardPath(req);
      if (!filePath) {
        res.status(400).json({ error: { message: 'File path required', code: 'VALIDATION_ERROR' } });
        return;
      }

      // Per-file access check
      const userRoles = getUserRoles(req.user!);
      const canEdit = await docsAccessService.canUserEdit(req.user!.id, userRoles, filePath);
      if (!canEdit) {
        res.status(403).json({ error: { message: 'You do not have edit access to this document', code: 'DOC_ACCESS_DENIED' } });
        return;
      }

      const { content } = req.body as { content?: string };
      if (typeof content !== 'string') {
        res.status(400).json({ error: { message: 'Content string required', code: 'VALIDATION_ERROR' } });
        return;
      }
      await docsFilesService.writeFileContent(filePath, content);
      // Invalidate collaboration state so next session starts fresh from disk
      docsCollabService.invalidateDocument(filePath).catch(() => {});
      // Fire-and-forget: commit to Gitea for version history
      const userName = req.user!.email;
      docsHistoryService.commitFile(filePath, content, userName, req.user!.email).catch(() => {});
      res.json({ success: true, path: filePath });
    } catch (err) {
      handleFileError(err, res, next);
    }
  },
);

// POST /api/docs/files/* — create new file or folder
router.post(
  '/files/*',
  requireRole(...CONTENT_ROLES),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      cm_docs_operations.inc({ operation: 'create' });
      const filePath = extractWildcardPath(req);
      if (!filePath) {
        res.status(400).json({ error: { message: 'File path required', code: 'VALIDATION_ERROR' } });
        return;
      }

      // Per-file access check (matches PUT/DELETE guards)
      const userRoles = getUserRoles(req.user!);
      const canEdit = await docsAccessService.canUserEdit(req.user!.id, userRoles, filePath);
      if (!canEdit) {
        res.status(403).json({ error: { message: 'You do not have edit access to this directory', code: 'DOC_ACCESS_DENIED' } });
        return;
      }

      const { content, isDirectory } = req.body as { content?: string; isDirectory?: boolean };
      await docsFilesService.createFile(filePath, content, isDirectory);
      res.status(201).json({ success: true, path: filePath });
    } catch (err) {
      handleFileError(err, res, next);
    }
  },
);

// DELETE /api/docs/files/* — delete file or empty folder
router.delete(
  '/files/*',
  requireRole(...CONTENT_ROLES),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      cm_docs_operations.inc({ operation: 'delete' });
      const filePath = extractWildcardPath(req);
      if (!filePath) {
        res.status(400).json({ error: { message: 'File path required', code: 'VALIDATION_ERROR' } });
        return;
      }

      // Per-file access check
      const userRoles = getUserRoles(req.user!);
      const canEdit = await docsAccessService.canUserEdit(req.user!.id, userRoles, filePath);
      if (!canEdit) {
        res.status(403).json({ error: { message: 'You do not have edit access to this document', code: 'DOC_ACCESS_DENIED' } });
        return;
      }

      await docsFilesService.deleteFile(filePath);
      // Cascade delete for access policies and share links
      docsAccessService.cascadeDelete(filePath).catch(() => {});
      // Invalidate collaboration state for deleted file
      docsCollabService.invalidateDocument(filePath).catch(() => {});
      res.json({ success: true });
    } catch (err) {
      handleFileError(err, res, next);
    }
  },
);

/**
 * Extract the wildcard path from Express 5 req.params.
 * Express 5 uses params[0] for * routes.
 */
function extractWildcardPath(req: Request): string {
  // Express 5: req.params is array-like for * routes
  const params = req.params as Record<string, string | string[]>;
  const wildcardParam = params[0] || params['0'];
  if (Array.isArray(wildcardParam)) return wildcardParam.join('/');
  return (wildcardParam as string) || '';
}

function handleFileError(err: unknown, res: Response, next: NextFunction): void {
  if (err instanceof PathTraversalError) {
    res.status(403).json({ error: { message: 'Path traversal not allowed', code: 'FORBIDDEN' } });
    return;
  }
  if (err instanceof FileNotFoundError) {
    res.status(404).json({ error: { message: (err as Error).message, code: 'NOT_FOUND' } });
    return;
  }
  if ((err as Error).message?.startsWith('Already exists')) {
    res.status(409).json({ error: { message: (err as Error).message, code: 'CONFLICT' } });
    return;
  }
  if ((err as Error).message === 'Directory is not empty') {
    res.status(400).json({ error: { message: 'Directory is not empty', code: 'VALIDATION_ERROR' } });
    return;
  }
  logger.error('Docs file operation failed', err);
  next(err);
}

// --- Blog Endpoints ---

// GET /api/docs/blog/authors — read .authors.yml
router.get(
  '/blog/authors',
  requireRole(...CONTENT_ROLES),
  async (_req: Request, res: Response, next: NextFunction) => {
    try {
      const authors = await blogService.readAuthorsFile();
      res.json({ authors });
    } catch (err) {
      logger.error('Failed to read blog authors', err);
      next(err);
    }
  },
);

// PUT /api/docs/blog/authors — update .authors.yml
router.put(
  '/blog/authors',
  requireRole(...CONTENT_ROLES),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const { authors } = req.body as { authors?: Record<string, unknown> };
      if (!authors || typeof authors !== 'object') {
        res.status(400).json({ error: { message: 'Authors object required', code: 'VALIDATION_ERROR' } });
        return;
      }
      const parsed = authorsFileSchema.safeParse({ authors });
      if (!parsed.success) {
        res.status(400).json({ error: { message: 'Invalid authors data', code: 'VALIDATION_ERROR', details: parsed.error.flatten().fieldErrors } });
        return;
      }
      await blogService.writeAuthorsFile(parsed.data.authors);
      res.json({ success: true });
    } catch (err) {
      logger.error('Failed to update blog authors', err);
      next(err);
    }
  },
);

// GET /api/docs/blog/categories — deduplicated categories from posts
router.get(
  '/blog/categories',
  requireRole(...CONTENT_ROLES),
  async (_req: Request, res: Response, next: NextFunction) => {
    try {
      const categories = await blogService.extractCategories();
      res.json({ categories });
    } catch (err) {
      logger.error('Failed to extract blog categories', err);
      next(err);
    }
  },
);

// POST /api/docs/blog/posts — create new blog post from wizard
router.post(
  '/blog/posts',
  requireRole(...CONTENT_ROLES),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const parsed = newBlogPostSchema.safeParse(req.body);
      if (!parsed.success) {
        res.status(400).json({ error: { message: 'Invalid blog post data', code: 'VALIDATION_ERROR', details: parsed.error.flatten().fieldErrors } });
        return;
      }
      const { content, suggestedPath } = blogService.scaffoldBlogPost(parsed.data);
      await docsFilesService.createFile(suggestedPath, content);
      // Fire-and-forget: commit to Gitea
      const userName = req.user!.email;
      docsHistoryService.commitFile(suggestedPath, content, userName, req.user!.email).catch(() => {});
      res.status(201).json({ success: true, path: suggestedPath });
    } catch (err) {
      handleFileError(err, res, next);
    }
  },
);

// --- Version History Endpoints ---

// GET /api/docs/history/* — get commit history for a file
router.get(
  '/history/*',
  requireRole(...CONTENT_ROLES),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const filePath = extractWildcardPath(req);
      if (!filePath) {
        res.status(400).json({ error: { message: 'File path required', code: 'VALIDATION_ERROR' } });
        return;
      }
      const limit = Math.min(Math.max(Number(req.query['limit']) || 20, 1), 50);
      const commits = await docsHistoryService.getFileHistory(filePath, limit);
      res.json({ commits });
    } catch (err) {
      logger.error('Failed to get docs history', err);
      next(err);
    }
  },
);

// GET /api/docs/revision/:sha/* — get file content at specific commit
router.get(
  '/revision/:sha/*',
  requireRole(...CONTENT_ROLES),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const sha = req.params.sha as string;
      // Extract path after /revision/:sha/
      const params = req.params as Record<string, string | string[]>;
      const wildcardParam = params[0] || params['0'];
      const filePath = Array.isArray(wildcardParam) ? wildcardParam.join('/') : (wildcardParam || '');

      if (!filePath || !sha) {
        res.status(400).json({ error: { message: 'SHA and file path required', code: 'VALIDATION_ERROR' } });
        return;
      }
      const content = await docsHistoryService.getFileAtCommit(filePath, sha);
      if (content === null) {
        res.status(404).json({ error: { message: 'Revision not found', code: 'NOT_FOUND' } });
        return;
      }
      res.json({ sha, path: filePath, content });
    } catch (err) {
      logger.error('Failed to get docs revision', err);
      next(err);
    }
  },
);

// POST /api/docs/restore/:sha/* — restore file to a previous version
router.post(
  '/restore/:sha/*',
  requireRole(...CONTENT_ROLES),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const sha = req.params.sha as string;
      const params = req.params as Record<string, string | string[]>;
      const wildcardParam = params[0] || params['0'];
      const filePath = Array.isArray(wildcardParam) ? wildcardParam.join('/') : (wildcardParam || '');

      if (!filePath || !sha) {
        res.status(400).json({ error: { message: 'SHA and file path required', code: 'VALIDATION_ERROR' } });
        return;
      }

      const userName = req.user!.email;
      const content = await docsHistoryService.restoreRevision(filePath, sha, userName, req.user!.email);
      if (content === null) {
        res.status(404).json({ error: { message: 'Could not restore revision', code: 'RESTORE_FAILED' } });
        return;
      }

      // Also update the disk file
      await docsFilesService.writeFileContent(filePath, content);
      docsCollabService.invalidateDocument(filePath).catch(() => {});

      res.json({ success: true, path: filePath });
    } catch (err) {
      logger.error('Failed to restore docs revision', err);
      next(err);
    }
  },
);

// --- Templates ---

// GET /api/docs/templates — list available templates
router.get(
  '/templates',
  requireRole(...CONTENT_ROLES),
  async (_req: Request, res: Response) => {
    const templates = docsTemplatesService.getTemplates();
    res.json({ templates });
  },
);

// --- Metadata Dashboard ---

// GET /api/docs/metadata — full metadata for all docs pages
router.get(
  '/metadata',
  requireRole(...CONTENT_ROLES),
  async (_req: Request, res: Response, next: NextFunction) => {
    try {
      const metadata = await docsMetadataService.getMetadata();
      res.json(metadata);
    } catch (err) {
      logger.error('Failed to get docs metadata', err);
      next(err);
    }
  },
);

export const docsRouter = router;
