import { stat } from 'fs/promises';
import { resolve as pathResolve } from 'path';
import { parse as yamlParse } from 'yaml';
import { env } from '../../config/env';
import { redis } from '../../config/redis';
import { prisma } from '../../config/database';
import { logger } from '../../utils/logger';
import { docsFilesService, type FileNode } from './docs-files.service';

const METADATA_CACHE_KEY = 'DOCS_CACHE:metadata';
const METADATA_CACHE_TTL = 300; // 5 minutes

const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;

export interface DocPageMetadata {
  path: string;
  title: string | null;
  tags: string[];
  description: string | null;
  status: string | null;
  lastModified: string | null;
  wordCount: number;
  hasAccessPolicy: boolean;
}

export interface DocMetadataWarning {
  type: 'no-tags' | 'no-description' | 'orphaned' | 'stale';
  paths: string[];
}

export interface DocMetadataResult {
  totalPages: number;
  pages: DocPageMetadata[];
  warnings: DocMetadataWarning[];
}

/**
 * Get comprehensive metadata for all documentation pages.
 * Walks the file tree, parses frontmatter, cross-references with analytics and nav.
 */
async function getMetadata(): Promise<DocMetadataResult> {
  // Try cache
  try {
    const cached = await redis.get(METADATA_CACHE_KEY);
    if (cached) return JSON.parse(cached) as DocMetadataResult;
  } catch {
    // Ignore cache errors
  }

  const tree = await docsFilesService.listTree();
  const docsRoot = pathResolve(env.MKDOCS_DOCS_PATH);

  // Collect all .md files
  const mdFiles: string[] = [];
  function walk(nodes: FileNode[]) {
    for (const node of nodes) {
      if (node.isDirectory && node.children) {
        walk(node.children);
      } else if (node.name.endsWith('.md')) {
        mdFiles.push(node.path);
      }
    }
  }
  walk(tree);

  // Get access policies in one query
  const policies = await prisma.docAccessPolicy.findMany({
    select: { documentPath: true },
  });
  const policyPaths = new Set(policies.map(p => p.documentPath));

  // Parse each file
  const pages: DocPageMetadata[] = await Promise.all(
    mdFiles.map(async (filePath) => {
      try {
        const content = await docsFilesService.readFileContent(filePath);
        const fullPath = pathResolve(docsRoot, filePath);

        // Get file mod time
        let lastModified: string | null = null;
        try {
          const fileStats = await stat(fullPath);
          lastModified = fileStats.mtime.toISOString();
        } catch {
          // Ignore stat errors
        }

        // Parse frontmatter
        let title: string | null = null;
        let tags: string[] = [];
        let description: string | null = null;
        let status: string | null = null;

        const match = content.match(FRONTMATTER_REGEX);
        if (match) {
          try {
            const fm = yamlParse(match[1]) as Record<string, unknown>;
            if (typeof fm?.title === 'string') title = fm.title;
            if (Array.isArray(fm?.tags)) tags = fm.tags.filter((t): t is string => typeof t === 'string');
            if (typeof fm?.description === 'string') description = fm.description;
            if (typeof fm?.status === 'string') status = fm.status;
          } catch {
            // Invalid frontmatter
          }
        }

        // Extract title from first # heading if not in frontmatter
        if (!title) {
          const headingMatch = content.match(/^#\s+(.+)$/m);
          if (headingMatch) title = headingMatch[1].trim();
        }

        // Word count (body only, rough estimate)
        const bodyText = match ? match[2] : content;
        const wordCount = bodyText.split(/\s+/).filter(w => w.length > 0).length;

        return {
          path: filePath,
          title,
          tags,
          description,
          status,
          lastModified,
          wordCount,
          hasAccessPolicy: policyPaths.has(filePath),
        };
      } catch {
        return {
          path: filePath,
          title: null,
          tags: [],
          description: null,
          status: null,
          lastModified: null,
          wordCount: 0,
          hasAccessPolicy: false,
        };
      }
    }),
  );

  // Build warnings
  const warnings: DocMetadataWarning[] = [];

  const noTags = pages.filter(p => p.tags.length === 0 && !p.path.startsWith('blog/')).map(p => p.path);
  if (noTags.length > 0) warnings.push({ type: 'no-tags', paths: noTags });

  const noDescription = pages.filter(p => !p.description && !p.path.startsWith('blog/')).map(p => p.path);
  if (noDescription.length > 0) warnings.push({ type: 'no-description', paths: noDescription });

  const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
  const stale = pages.filter(p => p.lastModified && p.lastModified < thirtyDaysAgo).map(p => p.path);
  if (stale.length > 0) warnings.push({ type: 'stale', paths: stale });

  const result: DocMetadataResult = {
    totalPages: pages.length,
    pages,
    warnings,
  };

  // Cache result
  try {
    await redis.setex(METADATA_CACHE_KEY, METADATA_CACHE_TTL, JSON.stringify(result));
  } catch {
    // Ignore cache errors
  }

  return result;
}

export const docsMetadataService = {
  getMetadata,
};
