import {
  ConflictException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { AuditAction, AuditEntityType } from '@aechr/shared';
import { AssessmentType, Prisma } from '@prisma/client';
import type { JwtAuthUser } from '../../common/interfaces/jwt-auth-user.interface';
import {
  buildPagination,
  buildSoftDeleteWhere,
} from '../../common/prisma/pagination';
import { AuditService } from '../audit/audit.service';
import { PrismaService } from '../prisma/prisma.service';
import { AttachTemplateQuestionDto } from './dto/attach-template-question.dto';
import { CreateTemplateDto } from './dto/create-template.dto';
import { ReorderTemplateQuestionsDto } from './dto/reorder-template-questions.dto';
import { TemplateQueryDto } from './dto/template-query.dto';
import { UpdateTemplateDto } from './dto/update-template.dto';

@Injectable()
export class TemplatesService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly auditService: AuditService,
  ) {}

  async list(query: TemplateQueryDto) {
    const { skip, take, meta } = buildPagination(query);
    const where: Prisma.AssessmentTemplateWhereInput = {
      ...buildSoftDeleteWhere(query.includeDeleted),
      ...(query.assessmentType ? { assessmentType: query.assessmentType } : {}),
      ...(query.isActive !== undefined ? { isActive: query.isActive } : {}),
      ...(query.search
        ? {
            OR: [
              { name: { contains: query.search } },
              { code: { contains: query.search } },
            ],
          }
        : {}),
    };

    const [items, total] = await this.prisma.$transaction([
      this.prisma.assessmentTemplate.findMany({
        where,
        skip,
        take,
        orderBy: { createdAt: query.sortOrder },
      }),
      this.prisma.assessmentTemplate.count({ where }),
    ]);

    return { items, total, ...meta };
  }

  async create(dto: CreateTemplateDto, actor: JwtAuthUser) {
    const existing = await this.prisma.assessmentTemplate.findUnique({
      where: { code: dto.code },
    });
    if (existing && !existing.deletedAt) {
      throw new ConflictException('Template code already exists');
    }

    const template = await this.prisma.assessmentTemplate.create({
      data: {
        name: dto.name,
        code: dto.code,
        assessmentType: dto.assessmentType,
        description: dto.description,
        version: dto.version,
        isActive: dto.isActive,
        createdBy: actor.sub,
        updatedBy: actor.sub,
      },
    });

    await this.auditService.log({
      module: 'templates',
      action: AuditAction.CREATE,
      entityType: AuditEntityType.TEMPLATE,
      entityId: template.id,
      newValues: dto as unknown as Prisma.InputJsonValue,
      createdBy: actor.sub,
      updatedBy: actor.sub,
    });

    return template;
  }

  async findOne(id: string) {
    const template = await this.prisma.assessmentTemplate.findUnique({
      where: { id },
      include: {
        questions: {
          where: { deletedAt: null },
          include: {
            question: true,
          },
          orderBy: { sortOrder: 'asc' },
        },
      },
    });
    if (!template) {
      throw new NotFoundException('Template not found');
    }
    return template;
  }

  async update(id: string, dto: UpdateTemplateDto, actor: JwtAuthUser) {
    const existing = await this.findOne(id);
    const updated = await this.prisma.assessmentTemplate.update({
      where: { id },
      data: {
        name: dto.name,
        code: dto.code,
        assessmentType: dto.assessmentType,
        description: dto.description,
        version: dto.version,
        isActive: dto.isActive,
        updatedBy: actor.sub,
      },
    });

    await this.auditService.log({
      module: 'templates',
      action: AuditAction.UPDATE,
      entityType: AuditEntityType.TEMPLATE,
      entityId: id,
      oldValues: {
        name: existing.name,
        code: existing.code,
        assessmentType: existing.assessmentType,
      } as Prisma.InputJsonValue,
      newValues: dto as unknown as Prisma.InputJsonValue,
      createdBy: actor.sub,
      updatedBy: actor.sub,
    });

    return updated;
  }

  async remove(id: string, actor: JwtAuthUser) {
    await this.findOne(id);
    const deleted = await this.prisma.assessmentTemplate.update({
      where: { id },
      data: {
        deletedAt: new Date(),
        deletedBy: actor.sub,
        updatedBy: actor.sub,
      },
    });

    await this.auditService.log({
      module: 'templates',
      action: AuditAction.DELETE,
      entityType: AuditEntityType.TEMPLATE,
      entityId: id,
      remarks: 'Template soft deleted',
      createdBy: actor.sub,
      updatedBy: actor.sub,
      deletedBy: actor.sub,
    });

    return deleted;
  }

  async restore(id: string, actor: JwtAuthUser) {
    await this.findOne(id);
    const restored = await this.prisma.assessmentTemplate.update({
      where: { id },
      data: {
        deletedAt: null,
        deletedBy: null,
        updatedBy: actor.sub,
      },
    });

    await this.auditService.log({
      module: 'templates',
      action: AuditAction.RESTORE,
      entityType: AuditEntityType.TEMPLATE,
      entityId: id,
      remarks: 'Template restored',
      createdBy: actor.sub,
      updatedBy: actor.sub,
    });

    return restored;
  }

  async attachQuestion(
    id: string,
    dto: AttachTemplateQuestionDto,
    actor: JwtAuthUser,
  ) {
    await this.findOne(id);
    const question = await this.prisma.questionBank.findFirst({
      where: {
        id: dto.questionId,
        deletedAt: null,
      },
    });

    if (!question) {
      throw new NotFoundException('Question not found');
    }

    const existing = await this.prisma.assessmentTemplateQuestion.findFirst({
      where: { templateId: id, questionId: dto.questionId },
    });

    if (existing && !existing.deletedAt) {
      throw new ConflictException('Question already attached to template');
    }

    if (existing && existing.deletedAt) {
      const restored = await this.prisma.assessmentTemplateQuestion.update({
        where: { id: existing.id },
        data: {
          sortOrder: dto.sortOrder,
          sectionName: dto.sectionName,
          isRequiredOverride: dto.isRequiredOverride,
          configJson: dto.configJson as Prisma.InputJsonValue | undefined,
          deletedAt: null,
          deletedBy: null,
          updatedBy: actor.sub,
        },
      });

      await this.auditService.log({
        module: 'templates',
        action: AuditAction.RESTORE,
        entityType: AuditEntityType.TEMPLATE,
        entityId: id,
        remarks: 'Template question mapping restored',
        newValues: dto as unknown as Prisma.InputJsonValue,
        createdBy: actor.sub,
        updatedBy: actor.sub,
      });

      return restored;
    }

    const mapping = await this.prisma.assessmentTemplateQuestion.create({
      data: {
        templateId: id,
        questionId: dto.questionId,
        sortOrder: dto.sortOrder,
        sectionName: dto.sectionName,
        isRequiredOverride: dto.isRequiredOverride,
        configJson: dto.configJson as Prisma.InputJsonValue | undefined,
        createdBy: actor.sub,
        updatedBy: actor.sub,
      },
    });

    await this.auditService.log({
      module: 'templates',
      action: AuditAction.CREATE,
      entityType: AuditEntityType.TEMPLATE,
      entityId: id,
      remarks: 'Question attached to template',
      newValues: dto as unknown as Prisma.InputJsonValue,
      createdBy: actor.sub,
      updatedBy: actor.sub,
    });

    return mapping;
  }

  async reorderQuestions(
    id: string,
    dto: ReorderTemplateQuestionsDto,
    actor: JwtAuthUser,
  ) {
    await this.findOne(id);

    const mappings = await this.prisma.assessmentTemplateQuestion.findMany({
      where: {
        templateId: id,
        deletedAt: null,
        id: { in: dto.items.map((item) => item.mappingId) },
      },
    });

    if (mappings.length !== dto.items.length) {
      throw new NotFoundException('One or more template question mappings were not found');
    }

    await this.prisma.$transaction(
      dto.items.map((item) =>
        this.prisma.assessmentTemplateQuestion.update({
          where: { id: item.mappingId },
          data: {
            sortOrder: item.sortOrder,
            updatedBy: actor.sub,
          },
        }),
      ),
    );

    await this.auditService.log({
      module: 'templates',
      action: AuditAction.UPDATE,
      entityType: AuditEntityType.TEMPLATE,
      entityId: id,
      remarks: 'Template questions reordered',
      newValues: dto.items as unknown as Prisma.InputJsonValue,
      createdBy: actor.sub,
      updatedBy: actor.sub,
    });

    return this.findOne(id);
  }

  async removeQuestion(id: string, mappingId: string, actor: JwtAuthUser) {
    await this.findOne(id);
    const mapping = await this.prisma.assessmentTemplateQuestion.findFirst({
      where: {
        id: mappingId,
        templateId: id,
      },
    });
    if (!mapping) {
      throw new NotFoundException('Template question mapping not found');
    }

    const deleted = await this.prisma.assessmentTemplateQuestion.update({
      where: { id: mappingId },
      data: {
        deletedAt: new Date(),
        deletedBy: actor.sub,
        updatedBy: actor.sub,
      },
    });

    await this.auditService.log({
      module: 'templates',
      action: AuditAction.DELETE,
      entityType: AuditEntityType.TEMPLATE,
      entityId: id,
      remarks: 'Template question mapping deleted',
      createdBy: actor.sub,
      updatedBy: actor.sub,
      deletedBy: actor.sub,
    });

    return deleted;
  }

  async restoreQuestion(id: string, mappingId: string, actor: JwtAuthUser) {
    await this.findOne(id);
    const mapping = await this.prisma.assessmentTemplateQuestion.findFirst({
      where: {
        id: mappingId,
        templateId: id,
      },
    });
    if (!mapping) {
      throw new NotFoundException('Template question mapping not found');
    }

    const question = await this.prisma.questionBank.findFirst({
      where: {
        id: mapping.questionId,
        deletedAt: null,
      },
    });
    if (!question) {
      throw new ConflictException('Deleted questions cannot be reattached to templates');
    }

    const restored = await this.prisma.assessmentTemplateQuestion.update({
      where: { id: mappingId },
      data: {
        deletedAt: null,
        deletedBy: null,
        updatedBy: actor.sub,
      },
    });

    await this.auditService.log({
      module: 'templates',
      action: AuditAction.RESTORE,
      entityType: AuditEntityType.TEMPLATE,
      entityId: id,
      remarks: 'Template question mapping restored',
      createdBy: actor.sub,
      updatedBy: actor.sub,
    });

    return restored;
  }
}
