import { Injectable, Logger, NotFoundException, OnModuleInit } from '@nestjs/common';
import { promises as fs } from 'node:fs';
import * as path from 'node:path';
import type { GlobalWord, WordWithAsset } from './global-word.interface';
import { ImageEngineService } from '../image-engine/image-engine.service';

@Injectable()
export class GlobalWordsService implements OnModuleInit {
  private readonly logger = new Logger(GlobalWordsService.name);
  private readonly repoRoot = path.resolve(process.cwd(), '..');
  private readonly contentRoot = path.join(this.repoRoot, 'content');
  private readonly wordsPath = path.join(this.contentRoot, 'palabras-globales.json');
  private readonly assetsDir = path.join(this.contentRoot, 'generated', 'global-words');
  private wordsCache: GlobalWord[] | null = null;

  constructor(private readonly imageEngine: ImageEngineService) {}

  async onModuleInit() {
    await fs.mkdir(this.assetsDir, { recursive: true });
    // Lanzamos prefetch en background.
    this.prefetchAll().catch((error) =>
      this.logger.error('No se pudieron precargar todas las imágenes', error as Error),
    );
  }

  private async loadWords(): Promise<GlobalWord[]> {
    if (!this.wordsCache) {
      const raw = await fs.readFile(this.wordsPath, 'utf8');
      this.wordsCache = JSON.parse(raw) as GlobalWord[];
    }
    return this.wordsCache;
  }

  private slugify(text: string) {
    return text
      .normalize('NFD')
      .replace(/[\u0300-\u036f]/g, '')
      .replace(/[^a-zA-Z0-9]+/g, '-')
      .replace(/^-+|-+$/g, '')
      .toLowerCase();
  }

  private readonly mimeByExtension: Record<string, string> = {
    png: 'image/png',
    webp: 'image/webp',
    jpg: 'image/jpeg',
    jpeg: 'image/jpeg',
    svg: 'image/svg+xml',
  };

  private async resolveExistingAsset(slug: string) {
    for (const ext of Object.keys(this.mimeByExtension)) {
      const candidate = path.join(this.assetsDir, `${slug}.${ext}`);
      try {
        await fs.access(candidate);
        return { path: candidate, mimeType: this.mimeByExtension[ext] };
      } catch {
        // keep iterating
      }
    }
    return null;
  }

  private buildPrompt(word: GlobalWord) {
    return `Ilustración infantil estilo acuarela, vibrante y amigable para niños pequeños. Palabra clave: "${word.palabra}". Tema: ${word.tema}. Categoría: ${word.categoria}. Descripción: ${word.descripcion}. Usa los colores azul y oro si es un elemento de fútbol o autos. Fondo suave, detalles simples.`;
  }

  async listWords(): Promise<WordWithAsset[]> {
    const words = await this.loadWords();
    return Promise.all(
      words.map(async (word) => {
        const slug = this.slugify(word.palabra);
        const existing = await this.resolveExistingAsset(slug);
        return {
          ...word,
          slug,
          imageUrl: existing ? `/global-words/${slug}/image` : null,
          imageStatus: existing ? 'ready' : 'pending',
        };
      }),
    );
  }

  private async findWordBySlug(slug: string): Promise<GlobalWord & { slug: string }> {
    const words = await this.loadWords();
    const found = words
      .map((word) => ({ ...word, slug: this.slugify(word.palabra) }))
      .find((word) => word.slug === slug);
    if (!found) {
      throw new NotFoundException(`No existe la palabra con slug ${slug}`);
    }
    return found;
  }

  async ensureImage(slug: string) {
    const word = await this.findWordBySlug(slug);
    const existing = await this.resolveExistingAsset(slug);
    if (existing) {
      return existing;
    }
    this.logger.log(`Generando imagen para ${word.palabra}`);
    const prompt = this.buildPrompt(word);
    try {
      const asset = await this.imageEngine.generate(word, slug, prompt);
      return asset;
    } catch (error) {
      this.logger.warn(
        `No se pudo generar ${word.palabra} con el motor local, usando placeholder. Motivo: ${
          (error as Error).message
        }`,
      );
      const svgBuffer = this.buildPlaceholder(word);
      const filePath = path.join(this.assetsDir, `${slug}.svg`);
      await fs.writeFile(filePath, svgBuffer);
      return { path: filePath, mimeType: this.mimeByExtension.svg };
    }
  }

  async prefetchAll() {
    const words = await this.loadWords();
    for (const word of words) {
      const slug = this.slugify(word.palabra);
      if (await this.resolveExistingAsset(slug)) continue;
      await this.ensureImage(slug);
      await new Promise((resolve) => setTimeout(resolve, 400)); // evita saturar API
    }
  }

  private buildPlaceholder(word: GlobalWord) {
    const background = word.categoria === 'fútbol' ? '#13347a' : '#ffe6b3';
    const accent = word.categoria === 'fútbol' ? '#f7c948' : '#1967d2';
    const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512">
      <defs>
        <linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
          <stop offset="0%" stop-color="${background}" stop-opacity="0.9"/>
          <stop offset="100%" stop-color="${accent}" stop-opacity="0.9"/>
        </linearGradient>
      </defs>
      <rect width="512" height="512" fill="url(#grad)" rx="42" ry="42"/>
      <text x="50%" y="45%" font-size="120" font-family="Nunito, Arial" font-weight="800" text-anchor="middle" fill="#ffffff">${word.palabra}</text>
      <text x="50%" y="62%" font-size="36" font-family="Nunito, Arial" text-anchor="middle" fill="#ffffff" opacity="0.85">${word.categoria}</text>
      <foreignObject x="56" y="320" width="400" height="160">
        <div xmlns="http://www.w3.org/1999/xhtml" style="font-size:28px;font-family:Nunito,Arial;text-align:center;color:#fff;">
          ${word.descripcion}
        </div>
      </foreignObject>
    </svg>`;
    return Buffer.from(svg);
  }
}
