import {
  ConflictException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { AuditAction, AuditEntityType } from '@aechr/shared';
import { Prisma, QuestionFieldType } 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 { CreateQuestionDto } from './dto/create-question.dto';
import { QuestionQueryDto } from './dto/question-query.dto';
import { UpdateQuestionDto } from './dto/update-question.dto';

const OPTION_BASED_TYPES = new Set<QuestionFieldType>([
  QuestionFieldType.SELECT,
  QuestionFieldType.MULTI_SELECT,
  QuestionFieldType.RADIO,
  QuestionFieldType.CHECKBOX,
  QuestionFieldType.YES_NO,
]);

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

  async list(query: QuestionQueryDto) {
    const { skip, take, meta } = buildPagination(query);
    const where: Prisma.QuestionBankWhereInput = {
      ...buildSoftDeleteWhere(query.includeDeleted),
      ...(query.fieldType ? { fieldType: query.fieldType } : {}),
      ...(query.isActive !== undefined ? { isActive: query.isActive } : {}),
      ...(query.search
        ? {
            OR: [
              { code: { contains: query.search } },
              { label: { contains: query.search } },
            ],
          }
        : {}),
    };

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

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

  async create(dto: CreateQuestionDto, actor: JwtAuthUser) {
    this.validateQuestionConfig(dto.fieldType, dto.optionsJson);

    const existing = await this.prisma.questionBank.findUnique({
      where: { code: dto.code },
    });
    if (existing && !existing.deletedAt) {
      throw new ConflictException('Question code already exists');
    }

    const question = await this.prisma.questionBank.create({
      data: {
        code: dto.code,
        label: dto.label,
        description: dto.description,
        helpText: dto.helpText,
        fieldType: dto.fieldType,
        placeholder: dto.placeholder,
        isRequired: dto.isRequired,
        optionsJson: dto.optionsJson as Prisma.InputJsonValue | undefined,
        validationJson: dto.validationJson as Prisma.InputJsonValue | undefined,
        isActive: dto.isActive,
        createdBy: actor.sub,
        updatedBy: actor.sub,
      },
    });

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

    return question;
  }

  async findOne(id: string) {
    const question = await this.prisma.questionBank.findUnique({ where: { id } });
    if (!question) {
      throw new NotFoundException('Question not found');
    }
    return question;
  }

  async update(id: string, dto: UpdateQuestionDto, actor: JwtAuthUser) {
    const existing = await this.findOne(id);
    this.validateQuestionConfig(dto.fieldType ?? existing.fieldType, dto.optionsJson ?? (existing.optionsJson as string[] | null) ?? undefined);

    const updated = await this.prisma.questionBank.update({
      where: { id },
      data: {
        code: dto.code,
        label: dto.label,
        description: dto.description,
        helpText: dto.helpText,
        fieldType: dto.fieldType,
        placeholder: dto.placeholder,
        isRequired: dto.isRequired,
        optionsJson: dto.optionsJson as Prisma.InputJsonValue | undefined,
        validationJson: dto.validationJson as Prisma.InputJsonValue | undefined,
        isActive: dto.isActive,
        updatedBy: actor.sub,
      },
    });

    await this.auditService.log({
      module: 'questions',
      action: AuditAction.UPDATE,
      entityType: AuditEntityType.QUESTION,
      entityId: id,
      oldValues: {
        code: existing.code,
        label: existing.label,
        fieldType: existing.fieldType,
      } 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.questionBank.update({
      where: { id },
      data: {
        deletedAt: new Date(),
        deletedBy: actor.sub,
        updatedBy: actor.sub,
      },
    });

    await this.auditService.log({
      module: 'questions',
      action: AuditAction.DELETE,
      entityType: AuditEntityType.QUESTION,
      entityId: id,
      remarks: 'Question 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.questionBank.update({
      where: { id },
      data: {
        deletedAt: null,
        deletedBy: null,
        updatedBy: actor.sub,
      },
    });

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

    return restored;
  }

  private validateQuestionConfig(
    fieldType: QuestionFieldType,
    optionsJson?: string[] | null,
  ) {
    if (fieldType === QuestionFieldType.YES_NO) {
      return;
    }

    if (OPTION_BASED_TYPES.has(fieldType)) {
      if (!optionsJson || optionsJson.length === 0) {
        throw new ConflictException('Options are required for the selected field type');
      }
      return;
    }

    if (optionsJson && optionsJson.length > 0) {
      throw new ConflictException('Options are only allowed for selectable field types');
    }
  }
}
