import { parse as yamlParse, stringify as yamlStringify } from 'yaml';
import { redis } from '../../config/redis';
import { logger } from '../../utils/logger';
import { docsFilesService, FileNotFoundError } from './docs-files.service';
import { authorsFileSchema, type AuthorsFile, type AuthorEntry, type NewBlogPost } from './blog.schemas';

const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
const CATEGORIES_CACHE_KEY = 'DOCS_CACHE:blog:categories';
const CATEGORIES_CACHE_TTL = 300; // 5 minutes

export interface ParsedFrontmatter {
  frontmatter: Record<string, unknown>;
  body: string;
  rawYaml: string;
}

/**
 * Parse YAML frontmatter from markdown content.
 * Returns null if no frontmatter block found.
 */
function parseFrontmatter(content: string): ParsedFrontmatter | null {
  const match = content.match(FRONTMATTER_REGEX);
  if (!match) return null;

  const rawYaml = match[1];
  const body = match[2];

  try {
    const frontmatter = yamlParse(rawYaml) as Record<string, unknown>;
    return { frontmatter: frontmatter || {}, body, rawYaml };
  } catch {
    return null;
  }
}

/**
 * Serialize frontmatter object + body back to markdown.
 * Uses YAML stringify with options to produce MkDocs Material-compatible output
 * (no quoted dates, block arrays).
 */
function serializeFrontmatter(frontmatter: Record<string, unknown>, body: string): string {
  // Order keys for consistent output
  const ordered: Record<string, unknown> = {};
  const keyOrder = ['date', 'authors', 'categories', 'tags', 'draft', 'slug', 'description'];
  for (const key of keyOrder) {
    if (frontmatter[key] !== undefined) ordered[key] = frontmatter[key];
  }
  // Include any extra keys not in the order
  for (const key of Object.keys(frontmatter)) {
    if (!(key in ordered)) ordered[key] = frontmatter[key];
  }

  const yaml = yamlStringify(ordered, {
    lineWidth: 0,
    defaultStringType: 'PLAIN',
    defaultKeyType: 'PLAIN',
  }).trimEnd();

  return `---\n${yaml}\n---\n${body}`;
}

/**
 * Read and parse the blog/.authors.yml file.
 */
async function readAuthorsFile(): Promise<Record<string, AuthorEntry>> {
  try {
    const content = await docsFilesService.readFileContent('blog/.authors.yml');
    const parsed = yamlParse(content) as { authors?: Record<string, AuthorEntry> };
    return parsed?.authors || {};
  } catch (err) {
    if (err instanceof FileNotFoundError) {
      // Create default empty authors file
      await docsFilesService.writeFileContent('blog/.authors.yml', 'authors: {}\n');
      return {};
    }
    throw err;
  }
}

/**
 * Write the blog/.authors.yml file.
 * Validates structure with Zod before writing.
 */
async function writeAuthorsFile(authors: Record<string, AuthorEntry>): Promise<void> {
  // Validate
  authorsFileSchema.parse({ authors });

  const yaml = yamlStringify({ authors }, {
    lineWidth: 0,
    defaultStringType: 'PLAIN',
    defaultKeyType: 'PLAIN',
  });

  await docsFilesService.writeFileContent('blog/.authors.yml', yaml);
}

/**
 * Extract all unique categories from existing blog posts.
 * Results are cached in Redis for 5 minutes.
 */
async function extractCategories(): Promise<string[]> {
  // Try cache
  try {
    const cached = await redis.get(CATEGORIES_CACHE_KEY);
    if (cached) return JSON.parse(cached) as string[];
  } catch {
    // Ignore cache errors
  }

  const tree = await docsFilesService.listTree();
  const categories = new Set<string>();

  // Find blog/posts directory
  function findBlogPosts(nodes: typeof tree): typeof tree {
    for (const node of nodes) {
      if (node.path === 'blog/posts' && node.isDirectory && node.children) {
        return node.children;
      }
      if (node.isDirectory && node.children) {
        const result = findBlogPosts(node.children);
        if (result.length > 0) return result;
      }
    }
    return [];
  }

  const posts = findBlogPosts(tree);

  // Read each post and extract categories
  await Promise.all(
    posts
      .filter(n => !n.isDirectory && n.name.endsWith('.md'))
      .map(async (node) => {
        try {
          const content = await docsFilesService.readFileContent(node.path);
          const parsed = parseFrontmatter(content);
          if (parsed?.frontmatter.categories && Array.isArray(parsed.frontmatter.categories)) {
            for (const cat of parsed.frontmatter.categories) {
              if (typeof cat === 'string') categories.add(cat);
            }
          }
        } catch {
          // Skip files that can't be read
        }
      }),
  );

  const sorted = Array.from(categories).sort();

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

  return sorted;
}

/**
 * Scaffold a new blog post with frontmatter.
 */
function scaffoldBlogPost(opts: NewBlogPost): { content: string; suggestedPath: string } {
  const slug = opts.slug || slugify(opts.title);
  const frontmatter: Record<string, unknown> = {
    date: opts.date,
    authors: opts.authors,
  };
  if (opts.categories.length > 0) frontmatter.categories = opts.categories;
  if (opts.draft) frontmatter.draft = true;
  if (opts.slug) frontmatter.slug = opts.slug;
  if (opts.description) frontmatter.description = opts.description;

  const body = `\n# ${opts.title}\n\nWrite your post content here.\n\n<!-- more -->\n\nContinue writing below the fold...\n`;
  const content = serializeFrontmatter(frontmatter, body);
  const suggestedPath = `blog/posts/${opts.date}-${slug}.md`;

  return { content, suggestedPath };
}

function slugify(text: string): string {
  return text
    .toLowerCase()
    .replace(/[^a-z0-9\s-]/g, '')
    .replace(/\s+/g, '-')
    .replace(/-+/g, '-')
    .replace(/^-|-$/g, '')
    .substring(0, 80);
}

export const blogService = {
  parseFrontmatter,
  serializeFrontmatter,
  readAuthorsFile,
  writeAuthorsFile,
  extractCategories,
  scaffoldBlogPost,
};
