import { randomUUID } from "node:crypto";
import { Injectable } from "@nestjs/common";
import { db } from "../config/database.js";
import { attendanceRepository } from "./attendance.repository.js";
import { outboxRepository } from "../shared/outbox/outbox.repository.js";
import { buildEvent, serializeEvent } from "../shared/outbox/event-builder.js";
import { isValidAttendanceStatus } from "./attendance-status.js";
import {
  NotFoundError,
  ValidationError,
  ConflictError,
} from "../shared/errors/application-error.js";
import type { Attendance } from "./attendance.schema.js";

export interface RecordAttendanceInput {
  scheduleId: string;
  studentId: string;
  status: string;
  remark?: string;
  recordedBy: string;
  schoolId: string;
}

@Injectable()
export class AttendanceService {
  async recordAttendance(
    input: RecordAttendanceInput,
    userId?: string,
  ): Promise<{ id: string }> {
    if (
      !input.scheduleId ||
      !input.studentId ||
      !input.recordedBy ||
      !input.schoolId
    ) {
      throw new ValidationError(
        "scheduleId, studentId, recordedBy, schoolId are required",
      );
    }
    if (!isValidAttendanceStatus(input.status)) {
      throw new ValidationError(
        `Invalid attendance status: ${input.status}. Must be one of: present, absent, late, leave`,
      );
    }

    // Idempotency: check existing record
    const existing = await attendanceRepository.findByScheduleAndStudent(
      input.scheduleId,
      input.studentId,
    );
    if (existing) {
      throw new ConflictError(
        `Attendance already recorded for student ${input.studentId} in schedule ${input.scheduleId}`,
      );
    }

    const id = randomUUID();
    const event = buildEvent({
      aggregateId: id,
      eventType: "attendance.recorded",
      payload: {
        attendanceId: id,
        scheduleId: input.scheduleId,
        studentId: input.studentId,
        status: input.status,
      },
      userId,
    });

    await db.transaction(async (tx) => {
      await attendanceRepository.create(
        {
          id,
          scheduleId: input.scheduleId,
          studentId: input.studentId,
          status: input.status,
          remark: input.remark,
          recordedBy: input.recordedBy,
          schoolId: input.schoolId,
        },
        tx,
      );
      await outboxRepository.create(
        {
          id: randomUUID(),
          eventId: event.event_id,
          aggregateId: id,
          aggregateType: "attendance",
          eventType: "attendance.recorded",
          occurredAt: new Date(event.occurred_at),
          payload: serializeEvent(event),
          status: "pending",
        },
        tx,
      );
    });

    return { id };
  }

  async getAttendance(id: string): Promise<Attendance> {
    const record = await attendanceRepository.findById(id);
    if (!record) {
      throw new NotFoundError(`Attendance ${id} not found`);
    }
    return record;
  }

  async listByStudent(studentId: string): Promise<Attendance[]> {
    return attendanceRepository.findByStudentId(studentId);
  }

  async listByClass(classId: string): Promise<Attendance[]> {
    return attendanceRepository.findByClassId(classId);
  }
}
