import { randomUUID } from "node:crypto";
import { Injectable, Logger } from "@nestjs/common";
import { knowledgePointsRepository } from "./knowledge-points.repository.js";
import type { KnowledgePoint } from "./knowledge-points.schema.js";
import { getNeo4jSession } from "../config/neo4j.js";
import {
  InternalError,
  NotFoundError,
  ValidationError,
} from "../shared/errors/application-error.js";

export interface CreateKnowledgePointInput {
  chapterId: string;
  title: string;
  description?: string;
}

export interface UpdateKnowledgePointInput {
  title?: string;
  description?: string;
}

export interface PrerequisiteNode {
  id: string;
  title: string;
}

@Injectable()
export class KnowledgePointsService {
  private readonly logger = new Logger(KnowledgePointsService.name);

  async createKnowledgePoint(
    input: CreateKnowledgePointInput,
  ): Promise<{ id: string }> {
    if (!input.chapterId || !input.title) {
      throw new ValidationError("chapterId, title are required");
    }

    const id = randomUUID();
    await knowledgePointsRepository.create({
      id,
      chapterId: input.chapterId,
      title: input.title,
      description: input.description,
    });

    // Neo4j：创建知识点节点。非阻塞——失败仅记录日志，不影响 MySQL 写入。
    await this.safeCreateNode(id, input.title);

    return { id };
  }

  async getKnowledgePoint(id: string): Promise<KnowledgePoint> {
    const kp = await knowledgePointsRepository.findById(id);
    if (!kp) {
      throw new NotFoundError("KnowledgePoint", id);
    }
    return kp;
  }

  async listByChapter(chapterId: string): Promise<KnowledgePoint[]> {
    return knowledgePointsRepository.findByChapterId(chapterId);
  }

  async updateKnowledgePoint(
    id: string,
    data: UpdateKnowledgePointInput,
  ): Promise<void> {
    await this.getKnowledgePoint(id);
    await knowledgePointsRepository.update(id, data);
  }

  async deleteKnowledgePoint(id: string): Promise<void> {
    await this.getKnowledgePoint(id);
    await knowledgePointsRepository.delete(id);
  }

  async getPrerequisites(
    knowledgePointId: string,
  ): Promise<PrerequisiteNode[]> {
    const session = getNeo4jSession();
    if (!session) {
      return [];
    }

    try {
      const result = await session.executeRead((tx) =>
        tx.run(
          `MATCH (kp:KnowledgePoint {id: $id})<-[:PREREQUISITE_OF*1..5]-(prereq)
           RETURN prereq.id as id, prereq.title as title`,
          { id: knowledgePointId },
        ),
      );
      return result.records.map((r): PrerequisiteNode => {
        const rawId: unknown = r.get("id");
        const rawTitle: unknown = r.get("title");
        return {
          id: typeof rawId === "string" ? rawId : String(rawId),
          title: typeof rawTitle === "string" ? rawTitle : String(rawTitle),
        };
      });
    } catch (err) {
      this.logger.warn(
        `Neo4j getPrerequisites failed: ${err instanceof Error ? err.message : String(err)}`,
      );
      return [];
    } finally {
      await session.close();
    }
  }

  async addPrerequisite(id: string, prerequisiteId: string): Promise<void> {
    if (id === prerequisiteId) {
      throw new ValidationError(
        "A knowledge point cannot be a prerequisite of itself",
      );
    }

    // 先校验两个知识点在 MySQL 中都存在
    await this.getKnowledgePoint(id);
    await this.getKnowledgePoint(prerequisiteId);

    const session = getNeo4jSession();
    if (!session) {
      throw new InternalError(
        "Neo4j is not available, cannot add prerequisite",
      );
    }

    try {
      await session.executeWrite((tx) =>
        tx.run(
          `MATCH (kp:KnowledgePoint {id: $kpId}), (prereq:KnowledgePoint {id: $prereqId})
           MERGE (prereq)-[:PREREQUISITE_OF]->(kp)`,
          { kpId: id, prereqId: prerequisiteId },
        ),
      );
    } finally {
      await session.close();
    }
  }

  private async safeCreateNode(id: string, title: string): Promise<void> {
    const session = getNeo4jSession();
    if (!session) {
      return;
    }

    try {
      await session.executeWrite((tx) =>
        tx.run("MERGE (kp:KnowledgePoint {id: $id, title: $title})", {
          id,
          title,
        }),
      );
    } catch (err) {
      this.logger.warn(
        `Neo4j createNode failed (non-blocking): ${err instanceof Error ? err.message : String(err)}`,
      );
    } finally {
      await session.close();
    }
  }
}
