import { readFile } from "node:fs/promises";
import path from "node:path";
import yaml from "js-yaml";
import {
  DemoAppConfigSchema,
  DemoKnowledgeFileSchema,
  DemoScenarioFileSchema,
} from "@/lib/schemas";
import {
  formatScenarioDurationLabel,
  getScenarioTensionColor,
  toSimpleSlug,
} from "@/lib/scenario-utils";
import type {
  DemoAppConfig,
  DemoKnowledgeEntry,
  KnowledgeChunkMatch,
  ScenarioCatalogItem,
  ScenarioDefinition,
} from "@/lib/types";

const DEMO_DATA_DIR = path.join(process.cwd(), "data", "demo");
const DIFFICULTY_SORT_ORDER: Record<ScenarioDefinition["difficulty"], number> = {
  Fondamentaux: 0,
  "IntermÃ©diaire": 1,
  Intermediaire: 1,
  Exigeant: 2,
};

function getDataPath(fileName: string) {
  return path.join(DEMO_DATA_DIR, fileName);
}

async function readYamlFile(fileName: string) {
  const rawContent = await readFile(getDataPath(fileName), "utf8");

  try {
    return yaml.load(rawContent);
  } catch (error) {
    const message =
      error instanceof Error ? error.message : "Erreur YAML inconnue.";
    throw new Error(`Impossible de lire ${fileName}: ${message}`);
  }
}

export async function getDemoAppConfig(): Promise<DemoAppConfig> {
  return DemoAppConfigSchema.parse(await readYamlFile("app.yaml"));
}

function toScenarioCatalogItem(
  rawScenario: Omit<ScenarioDefinition, "durationLabel" | "tensionColor"> & {
    tags: string[];
  },
  index: number,
): ScenarioCatalogItem {
  const scenario = {
    ...rawScenario,
    durationLabel: formatScenarioDurationLabel(rawScenario.maxTurns),
    tensionColor: getScenarioTensionColor(rawScenario.difficulty),
  } satisfies ScenarioDefinition;

  return {
    ...scenario,
    templateId: rawScenario.id,
    versionId: `${rawScenario.id}-demo-v1`,
    versionNumber: 1,
    status: "published",
    tags: rawScenario.tags.map((label) => {
      const slug = toSimpleSlug(label, `tag-${index + 1}`);

      return {
        id: slug,
        label,
        slug,
        status: "active" as const,
        scenarioCount: 0,
        groupCount: 0,
      };
    }),
  };
}

export async function getDemoScenarios(): Promise<ScenarioCatalogItem[]> {
  const parsed = DemoScenarioFileSchema.parse(await readYamlFile("scenarios.yaml"));

  return parsed.scenarios
    .map((scenario, index) => ({
      scenario: toScenarioCatalogItem(scenario, index),
      index,
    }))
    .sort((left, right) => {
      const difficultyDelta =
        DIFFICULTY_SORT_ORDER[left.scenario.difficulty] -
        DIFFICULTY_SORT_ORDER[right.scenario.difficulty];

      if (difficultyDelta !== 0) {
        return difficultyDelta;
      }

      return left.index - right.index;
    })
    .map(({ scenario }) => scenario);
}

export async function getDemoScenarioById(id: string) {
  const scenarios = await getDemoScenarios();

  return scenarios.find((scenario) => scenario.id === id) ?? null;
}

export async function getDemoKnowledgeEntries(): Promise<DemoKnowledgeEntry[]> {
  const parsed = DemoKnowledgeFileSchema.parse(await readYamlFile("knowledge.yaml"));

  return parsed.entries.map((entry) => ({
    id: entry.id,
    title: entry.title,
    sourceLabel: entry.sourceLabel ?? null,
    content: entry.content,
    tags: entry.tags,
  }));
}

function normalizeToken(value: string) {
  return value
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, " ")
    .trim();
}

function tokenize(value: string) {
  return normalizeToken(value)
    .split(/\s+/)
    .filter((token) => token.length >= 3);
}

function scoreKnowledgeEntry(entry: DemoKnowledgeEntry, queryTokens: string[]) {
  if (queryTokens.length === 0) {
    return 0;
  }

  const haystack = normalizeToken(
    [entry.title, entry.sourceLabel ?? "", entry.content, entry.tags.join(" ")].join(
      " ",
    ),
  );

  let score = 0;

  for (const token of queryTokens) {
    if (haystack.includes(token)) {
      score += token.length > 6 ? 1.5 : 1;
    }
  }

  const keywordBonus = entry.tags.filter((tag) =>
    queryTokens.includes(normalizeToken(tag)),
  ).length;

  return score + keywordBonus * 0.5;
}

function excerptKnowledgeContent(content: string, queryTokens: string[]) {
  const normalized = content.replace(/\s+/g, " ").trim();

  if (normalized.length <= 280) {
    return normalized;
  }

  const lowered = normalized.toLowerCase();
  const firstTokenIndex = queryTokens
    .map((token) => lowered.indexOf(token.toLowerCase()))
    .filter((index) => index >= 0)
    .sort((left, right) => left - right)[0];

  if (typeof firstTokenIndex !== "number") {
    return `${normalized.slice(0, 277).trimEnd()}...`;
  }

  const start = Math.max(0, firstTokenIndex - 80);
  const end = Math.min(normalized.length, start + 280);
  const excerpt = normalized.slice(start, end).trim();

  return start > 0 || end < normalized.length ? `...${excerpt}...` : excerpt;
}

export async function searchDemoKnowledgeBase(
  query: string,
  options?: {
    limit?: number;
    minScore?: number;
  },
) {
  const queryTokens = tokenize(query);

  if (queryTokens.length === 0) {
    return [] as KnowledgeChunkMatch[];
  }

  const entries = await getDemoKnowledgeEntries();
  const limit = options?.limit ?? 6;
  const minScore = options?.minScore ?? 1.4;

  return entries
    .map((entry) => ({
      entry,
      score: scoreKnowledgeEntry(entry, queryTokens),
    }))
    .filter((candidate) => candidate.score >= minScore)
    .sort((left, right) => right.score - left.score)
    .slice(0, limit)
    .map(({ entry, score }, index) => ({
      chunkId: `${entry.id}-chunk-${index + 1}`,
      documentId: entry.id,
      documentTitle: entry.title,
      sourceLabel: entry.sourceLabel,
      chunkIndex: index,
      content: excerptKnowledgeContent(entry.content, queryTokens),
      similarity: Math.min(score / (queryTokens.length * 1.5 + 1), 0.98),
      metadata: {
        tags: entry.tags,
      },
    }));
}
