import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  Post,
  Put,
  Req,
} from "@nestjs/common";
import { z } from "zod";
import {
  ExamsService,
  type CreateExamInput,
  type UpdateExamInput,
} from "./exams.service.js";
import {
  Permissions,
  RequirePermission,
} from "../middleware/permission.guard.js";
import type { AuthenticatedRequest } from "../middleware/auth.middleware.js";
import {
  UnauthorizedError,
  ValidationError,
} from "../shared/errors/application-error.js";

const createExamSchema = z.object({
  classId: z.string().min(1),
  subjectId: z.string().min(1),
  title: z.string().min(1).max(200),
  description: z.string().optional(),
  examDate: z.string().min(1),
  duration: z.number().int().positive(),
  totalScore: z.string().min(1),
  schoolId: z.string().min(1),
});

const updateExamSchema = z.object({
  title: z.string().min(1).max(200).optional(),
  description: z.string().optional(),
  examDate: z.string().min(1).optional(),
  duration: z.number().int().positive().optional(),
  totalScore: z.string().min(1).optional(),
});

const answerInputSchema = z.object({
  questionId: z.string().min(1),
  answer: z.string(),
});

const scoreInputSchema = z.object({
  questionId: z.string().min(1),
  score: z.string().min(1),
  teacherComment: z.string().optional(),
});

const submitExamSchema = z.object({
  studentId: z.string().min(1),
  answers: z.array(answerInputSchema).default([]),
});

const gradeExamSchema = z.object({
  submissionId: z.string().min(1),
  scores: z.array(scoreInputSchema).min(1),
});

@Controller("v1/exams")
export class ExamsController {
  constructor(private readonly examsService: ExamsService) {}

  @Post()
  @RequirePermission(Permissions.EXAM_CREATE)
  async create(
    @Body() body: unknown,
    @Req() req: AuthenticatedRequest,
  ): Promise<{ success: true; data: { id: string } }> {
    const userId = req.userId;
    if (!userId) {
      throw new UnauthorizedError("Missing x-user-id header");
    }
    const parsed = createExamSchema.safeParse(body);
    if (!parsed.success) {
      throw new ValidationError("Invalid exam input", parsed.error.flatten());
    }
    const input: CreateExamInput = {
      ...parsed.data,
      examDate: new Date(parsed.data.examDate),
      createdBy: userId,
    };
    const result = await this.examsService.createExam(input, userId);
    return { success: true, data: result };
  }

  @Get(":id")
  @RequirePermission(Permissions.EXAM_READ)
  async findOne(@Param("id") id: string): Promise<{
    success: true;
    data: Awaited<ReturnType<ExamsService["getExam"]>>;
  }> {
    const data = await this.examsService.getExam(id);
    return { success: true, data };
  }

  @Get("class/:classId")
  @RequirePermission(Permissions.EXAM_READ)
  async listByClass(@Param("classId") classId: string): Promise<{
    success: true;
    data: Awaited<ReturnType<ExamsService["listExamsByClass"]>>;
  }> {
    const data = await this.examsService.listExamsByClass(classId);
    return { success: true, data };
  }

  @Put(":id")
  @RequirePermission(Permissions.EXAM_UPDATE)
  async update(
    @Param("id") id: string,
    @Body() body: unknown,
  ): Promise<{ success: true; data: { success: true } }> {
    const parsed = updateExamSchema.safeParse(body);
    if (!parsed.success) {
      throw new ValidationError(
        "Invalid exam update input",
        parsed.error.flatten(),
      );
    }
    const data: UpdateExamInput = {
      ...parsed.data,
      examDate: parsed.data.examDate
        ? new Date(parsed.data.examDate)
        : undefined,
    };
    await this.examsService.updateExam(id, data);
    return { success: true, data: { success: true } };
  }

  @Delete(":id")
  @RequirePermission(Permissions.EXAM_DELETE)
  async remove(
    @Param("id") id: string,
  ): Promise<{ success: true; data: { success: true } }> {
    await this.examsService.deleteExam(id);
    return { success: true, data: { success: true } };
  }

  @Post(":id/publish")
  @RequirePermission(Permissions.EXAM_PUBLISH)
  async publish(
    @Param("id") id: string,
    @Req() req: AuthenticatedRequest,
  ): Promise<{ success: true; data: { success: true } }> {
    const userId = req.userId;
    if (!userId) {
      throw new UnauthorizedError("Missing x-user-id header");
    }
    await this.examsService.publishExam(id, userId);
    return { success: true, data: { success: true } };
  }

  @Post(":id/submit")
  @RequirePermission(Permissions.EXAM_SUBMIT)
  async submit(
    @Param("id") id: string,
    @Body() body: unknown,
  ): Promise<{ success: true; data: { submissionId: string } }> {
    const parsed = submitExamSchema.safeParse(body);
    if (!parsed.success) {
      throw new ValidationError("Invalid submit input", parsed.error.flatten());
    }
    const result = await this.examsService.submitExam(
      id,
      parsed.data.studentId,
      parsed.data.answers,
    );
    return { success: true, data: result };
  }

  @Post(":id/grade")
  @RequirePermission(Permissions.EXAM_GRADE)
  async grade(
    @Param("id") id: string,
    @Body() body: unknown,
    @Req() req: AuthenticatedRequest,
  ): Promise<{ success: true; data: { totalScore: string } }> {
    const userId = req.userId;
    if (!userId) {
      throw new UnauthorizedError("Missing x-user-id header");
    }
    const parsed = gradeExamSchema.safeParse(body);
    if (!parsed.success) {
      throw new ValidationError("Invalid grade input", parsed.error.flatten());
    }
    const result = await this.examsService.gradeExam(
      id,
      parsed.data.submissionId,
      parsed.data.scores,
      userId,
    );
    return { success: true, data: result };
  }

  @Post(":id/archive")
  @RequirePermission(Permissions.EXAM_UPDATE)
  async archive(
    @Param("id") id: string,
    @Req() req: AuthenticatedRequest,
  ): Promise<{ success: true; data: { success: true } }> {
    const userId = req.userId;
    if (!userId) {
      throw new UnauthorizedError("Missing x-user-id header");
    }
    await this.examsService.archiveExam(id, userId);
    return { success: true, data: { success: true } };
  }
}
