import { Injectable, BadRequestException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Section, SectionDocument } from './schemas/section.schema';
import { Student, StudentDocument } from '../students/schemas/student.schema';
import { Class, ClassDocument } from '../classes/schemas/class.schema';
import { Teacher, TeacherDocument } from '../teachers/schemas/teacher.schema';
import { Model } from 'mongoose';
import { CreateSectionDto } from './dto/create-section.dto';
import { UpdateSectionDto } from './dto/update-section.dto';
import { toJSON } from '../common/utils/mongo-serializer';

@Injectable()
export class SectionsService {
  constructor(
    @InjectModel(Section.name) private sectionModel: Model<SectionDocument>,
    @InjectModel(Student.name) private studentModel: Model<StudentDocument>,
    @InjectModel(Class.name) private classModel: Model<ClassDocument>,
    @InjectModel(Teacher.name) private teacherModel: Model<TeacherDocument>,
  ) { }

  private norm(v: string) {
    return (v || '').trim();
  }
  private lc(v: string) {
    return (v || '').trim().toLowerCase();
  }

  /**
   * Format section name for display:
   * - ALL UPPERCASE input → preserved as-is (e.g. "XII", "A")
   * - Otherwise → Capitalized Case (e.g. "section a" → "Section A")
   */
  private formatSectionName(name: string): string {
    const trimmed = (name || '').trim();
    if (!trimmed) return trimmed;
    // If ALL UPPERCASE (and has at least one letter), preserve as-is
    if (trimmed === trimmed.toUpperCase() && trimmed !== trimmed.toLowerCase()) {
      return trimmed;
    }
    // Otherwise, capitalize first letter of each word
    return trimmed
      .toLowerCase()
      .replace(/\b[a-z]/g, c => c.toUpperCase());
  }

  private handleDup(err: any) {
    if (err?.code === 11000) {
      const fields = Object.keys(err?.keyPattern || {});
      if (fields.includes('name') && fields.includes('classId')) {
        throw new BadRequestException('Section already exists in this class');
      }
      if (fields.includes('classId') && fields.includes('order')) {
        throw new BadRequestException('Section order must be unique within the class');
      }
      throw new BadRequestException('Duplicate value');
    }
    throw err;
  }

  async create(dto: CreateSectionDto) {
    // Validate class exists
    const classId = this.norm(dto.classId);
    const classDoc = await this.classModel.findById(classId).lean();
    if (!classDoc) {
      throw new BadRequestException('Class not found');
    }

    // Validate teacher exists if provided
    if (dto.assignedTeacherId) {
      const teacherId = this.norm(dto.assignedTeacherId);
      const teacherDoc = await this.teacherModel.findById(teacherId).lean();
      if (!teacherDoc) {
        throw new BadRequestException('Teacher not found');
      }
    }

    try {
      const doc = await this.sectionModel.create({
        name: this.formatSectionName(dto.name),
        classId,
        assignedTeacherId: dto.assignedTeacherId ? this.norm(dto.assignedTeacherId) : undefined,
        order: dto.order,
      });
      return doc.toJSON();
    } catch (err) {
      this.handleDup(err);
    }
  }

  async findAll() {
    const sections = await this.sectionModel
      .find()
      .sort({ classId: 1, order: 1, name: 1 })
      .lean();
    return toJSON(sections);
  }

  async findByClass(classId: string) {
    const sections = await this.sectionModel
      .find({ classId: this.norm(classId) })
      .sort({ order: 1, name: 1 })
      .lean();
    return toJSON(sections);
  }

  async findOne(id: string) {
    const section = await this.sectionModel
      .findById(id)
      .lean();
    return toJSON(section);
  }

  async update(id: string, dto: UpdateSectionDto) {
    // Validate class exists if being updated
    if (dto.classId !== undefined) {
      const classId = this.norm(dto.classId);
      const classDoc = await this.classModel.findById(classId).lean();
      if (!classDoc) {
        throw new BadRequestException('Class not found');
      }
    }

    // Validate teacher exists if being updated (and not being cleared)
    if (dto.assignedTeacherId !== undefined && dto.assignedTeacherId) {
      const teacherId = this.norm(dto.assignedTeacherId);
      const teacherDoc = await this.teacherModel.findById(teacherId).lean();
      if (!teacherDoc) {
        throw new BadRequestException('Teacher not found');
      }
    }

    const $set: Record<string, any> = {};
    const $unset: Record<string, any> = {};

    if (dto.name !== undefined) $set.name = this.formatSectionName(dto.name);
    if (dto.classId !== undefined) $set.classId = this.norm(dto.classId);
    if (dto.assignedTeacherId !== undefined) {
      if (dto.assignedTeacherId) {
        $set.assignedTeacherId = this.norm(dto.assignedTeacherId);
      } else {
        $unset.assignedTeacherId = 1;
      }
    }
    if (dto.order !== undefined) $set.order = dto.order;

    const updateOps: Record<string, any> = {};
    if (Object.keys($set).length > 0) updateOps.$set = $set;
    if (Object.keys($unset).length > 0) updateOps.$unset = $unset;

    try {
      const section = await this.sectionModel
        .findByIdAndUpdate(id, updateOps, { new: true, runValidators: true })
        .lean();
      return toJSON(section);
    } catch (err) {
      this.handleDup(err);
    }
  }

  async delete(id: string) {
    // Check for dependent students
    const studentCount = await this.studentModel.countDocuments({ sectionId: id });
    if (studentCount > 0) {
      throw new BadRequestException(
        `Cannot delete section: ${studentCount} student(s) are assigned to this section. Please reassign or delete them first.`
      );
    }

    const section = await this.sectionModel
      .findByIdAndDelete(id)
      .lean();
    return toJSON(section);
  }
}
