import { randomUUID } from "node:crypto";
import { eq, and } from "drizzle-orm";
import { Injectable, type OnModuleInit } from "@nestjs/common";
import { db } from "../config/database.js";
import { teacherAssociations } from "../classes/teacher-associations.schema.js";
import { classes } from "../classes/classes.schema.js";
import { consumer } from "../config/kafka.js";
import { logger } from "../shared/observability/logger.js";

const IAM_TOPICS = [
  "edu.iam.teacher.assigned",
  "edu.iam.class.created",
] as const;

interface TeacherAssignedEvent {
  teacher_id: string;
  class_id: string;
  subject_id?: string;
  school_id: string;
}

interface ClassCreatedEvent {
  class_id: string;
  name: string;
  grade_id: string;
  head_teacher_id?: string;
  description?: string;
}

@Injectable()
export class IamConsumerService implements OnModuleInit {
  private isRunning = false;

  async onModuleInit(): Promise<void> {
    // Subscribe to IAM events (non-blocking - failures don't prevent service start)
    void this.start().catch((err) => {
      logger.warn(
        { err: err instanceof Error ? err.message : String(err) },
        "IAM consumer failed to start",
      );
    });
  }

  private async start(): Promise<void> {
    if (this.isRunning) return;

    try {
      for (const topic of IAM_TOPICS) {
        await consumer.subscribe({ topic, fromBeginning: false });
      }

      this.isRunning = true;
      logger.info({ topics: [...IAM_TOPICS] }, "IAM consumer subscribed");

      await consumer.run({
        eachMessage: async ({ topic, message }) => {
          try {
            const value = message.value?.toString();
            if (!value) return;

            const payload = JSON.parse(value) as Record<string, unknown>;

            if (topic === "edu.iam.teacher.assigned") {
              await this.handleTeacherAssigned(
                payload as unknown as TeacherAssignedEvent,
              );
            } else if (topic === "edu.iam.class.created") {
              await this.handleClassCreated(
                payload as unknown as ClassCreatedEvent,
              );
            }
          } catch (err) {
            logger.error(
              {
                err: err instanceof Error ? err.message : String(err),
                topic,
                offset: message.offset,
              },
              "IAM consumer message processing failed",
            );
          }
        },
      });
    } catch (err) {
      logger.warn(
        { err: err instanceof Error ? err.message : String(err) },
        "IAM consumer start failed",
      );
    }
  }

  /**
   * Handle teacher.assigned event - upsert teacher_associations (idempotent via unique index).
   */
  private async handleTeacherAssigned(
    event: TeacherAssignedEvent,
  ): Promise<void> {
    if (!event.teacher_id || !event.class_id || !event.school_id) {
      logger.warn(
        { event },
        "Invalid teacher.assigned event: missing required fields",
      );
      return;
    }

    // Check existing (idempotent)
    const conditions = [
      eq(teacherAssociations.teacherId, event.teacher_id),
      eq(teacherAssociations.classId, event.class_id),
    ];
    if (event.subject_id) {
      conditions.push(eq(teacherAssociations.subjectId, event.subject_id));
    }
    const [existing] = await db
      .select()
      .from(teacherAssociations)
      .where(and(...conditions))
      .limit(1);

    if (existing) {
      logger.debug(
        { teacherId: event.teacher_id, classId: event.class_id },
        "Teacher association already exists, skipping",
      );
      return;
    }

    await db.insert(teacherAssociations).values({
      id: randomUUID(),
      teacherId: event.teacher_id,
      classId: event.class_id,
      subjectId: event.subject_id,
      schoolId: event.school_id,
      source: "iam_event",
    });

    logger.info(
      { teacherId: event.teacher_id, classId: event.class_id },
      "Teacher association created from IAM event",
    );
  }

  /**
   * Handle class.created event - sync class record (idempotent via primary key).
   */
  private async handleClassCreated(event: ClassCreatedEvent): Promise<void> {
    if (!event.class_id || !event.name || !event.grade_id) {
      logger.warn(
        { event },
        "Invalid class.created event: missing required fields",
      );
      return;
    }

    // Check existing
    const [existing] = await db
      .select()
      .from(classes)
      .where(eq(classes.id, event.class_id))
      .limit(1);

    if (existing) {
      // Update if changed
      await db
        .update(classes)
        .set({
          name: event.name,
          gradeId: event.grade_id,
          headTeacherId: event.head_teacher_id,
          description: event.description,
        })
        .where(eq(classes.id, event.class_id));
      logger.debug({ classId: event.class_id }, "Class updated from IAM event");
      return;
    }

    await db.insert(classes).values({
      id: event.class_id,
      name: event.name,
      gradeId: event.grade_id,
      headTeacherId: event.head_teacher_id,
      description: event.description,
    });

    logger.info({ classId: event.class_id }, "Class created from IAM event");
  }
}
