import { createId } from "@paralleldrive/cuid2";
import { Injectable, Logger } from "@nestjs/common";
import { knowledgePointsRepository } from "./knowledge-points.repository.js";
import type {
  KnowledgePoint,
  NewKnowledgePoint,
} from "./knowledge-points.schema.js";
import { OutboxService } from "../shared/outbox/outbox.service.js";
import { AGGREGATE_TYPES, EVENT_TYPES } from "../shared/outbox/events.js";
import { getNeo4jSession } from "../config/neo4j.js";
import {
  NotFoundError,
  ValidationError,
} from "../shared/errors/application-error.js";

export interface CreateKnowledgePointInput {
  chapterId: string;
  title: string;
  description?: string;
  difficulty?: number;
  metadata?: Record<string, unknown> | null;
}

export interface UpdateKnowledgePointInput {
  title?: string;
  description?: string;
  difficulty?: number;
  metadata?: Record<string, unknown> | null;
}

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

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

  constructor(private readonly outbox: OutboxService) {}

  async createKnowledgePoint(
    input: CreateKnowledgePointInput,
  ): Promise<{ id: string }> {
    const id = createId();
    const record: NewKnowledgePoint = {
      id,
      chapterId: input.chapterId,
      title: input.title,
      description: input.description,
      difficulty: input.difficulty ?? 3,
      metadata: input.metadata ?? null,
    };
    await knowledgePointsRepository.create(record);

    // 发 Outbox 事件，由 Neo4j Sync Worker 异步创建图谱节点。
    // 禁止业务事务内同步双写 Neo4j（架构原则 §0.1 第 3 条）。
    await this.outbox.publish(
      EVENT_TYPES.KP_CREATED,
      AGGREGATE_TYPES.KNOWLEDGE_POINT,
      id,
      {
        chapter_id: record.chapterId,
        title: record.title,
        description: record.description,
        difficulty: record.difficulty,
      },
    );

    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> {
    const existing = await this.getKnowledgePoint(id);
    await knowledgePointsRepository.update(id, data);

    await this.outbox.publish(
      EVENT_TYPES.KP_UPDATED,
      AGGREGATE_TYPES.KNOWLEDGE_POINT,
      id,
      {
        title: data.title ?? existing.title,
        description: data.description ?? existing.description,
        difficulty: data.difficulty ?? existing.difficulty,
      },
    );
  }

  async deleteKnowledgePoint(id: string): Promise<void> {
    await this.getKnowledgePoint(id);
    await knowledgePointsRepository.delete(id);
    // 知识点删除事件也走 kp.updated（下游可标记节点为失效）
    await this.outbox.publish(
      EVENT_TYPES.KP_UPDATED,
      AGGREGATE_TYPES.KNOWLEDGE_POINT,
      id,
      { deleted: true },
    );
  }

  /**
   * 查询知识点前置依赖——读模型查询，直接读 Neo4j。
   * Neo4j 不可用时返回空数组，不阻塞主流程。
   */
  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();
    }
  }

  /**
   * 添加前置依赖——仅写 Outbox 事件，由 Neo4j Sync Worker 异步建立关系。
   * 不再直接写 Neo4j。
   */
  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);

    await this.outbox.publish(
      EVENT_TYPES.KP_PREREQUISITE_ADDED,
      AGGREGATE_TYPES.KNOWLEDGE_POINT,
      id,
      { prerequisite_id: prerequisiteId },
    );
  }

  /**
   * 移除前置依赖——仅写 Outbox 事件。
   */
  async removePrerequisite(id: string, prerequisiteId: string): Promise<void> {
    await this.getKnowledgePoint(id);
    await this.getKnowledgePoint(prerequisiteId);

    await this.outbox.publish(
      EVENT_TYPES.KP_PREREQUISITE_REMOVED,
      AGGREGATE_TYPES.KNOWLEDGE_POINT,
      id,
      { prerequisite_id: prerequisiteId },
    );
  }
}
