import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, Repository } from 'typeorm';
import { Question } from './question.entity';
import { QuestionOption } from '../question-options/question-option.entity';
import { CreateQuestionDto } from './dto/create-question.dto';
import { UpdateQuestionDto } from './dto/update-question.dto';
import { ReorderItemDto } from '../common/dto/reorder.dto';

@Injectable()
export class QuestionsService {
  constructor(
    @InjectRepository(Question)
    private readonly questionsRepo: Repository<Question>,
    @InjectRepository(QuestionOption)
    private readonly optionsRepo: Repository<QuestionOption>,
    private readonly dataSource: DataSource,
  ) {}

  /** Creates the question and its options together so a quiz is never half-saved. */
  async create(dto: CreateQuestionDto): Promise<Question> {
    return this.dataSource.transaction(async (manager) => {
      const { options, ...rest } = dto;
      const orderIndex = rest.orderIndex ?? (await this.nextOrderIndex(rest.activityId));
      const question = await manager.save(
        manager.create(Question, { ...rest, orderIndex }),
      );

      if (options?.length) {
        await manager.save(
          options.map((option, index) =>
            manager.create(QuestionOption, {
              questionId: question.id,
              optionText: option.optionText,
              isCorrect: !!option.isCorrect,
              orderIndex: option.orderIndex ?? index,
            }),
          ),
        );
      }

      return manager.findOne(Question, {
        where: { id: question.id },
        relations: ['options'],
      }) as Promise<Question>;
    });
  }

  findByActivity(activityId: number): Promise<Question[]> {
    return this.questionsRepo.find({
      where: { activityId },
      relations: ['options'],
      order: { orderIndex: 'ASC' },
    });
  }

  findById(id: number): Promise<Question | null> {
    return this.questionsRepo.findOne({ where: { id }, relations: ['options'] });
  }

  /** Options are replaced wholesale when supplied - simpler and race-free. */
  async update(id: number, dto: UpdateQuestionDto): Promise<Question> {
    const question = await this.findById(id);
    if (!question) throw new NotFoundException('Question not found');

    return this.dataSource.transaction(async (manager) => {
      const { options, ...rest } = dto;
      await manager.update(Question, id, rest);

      if (options) {
        await manager.delete(QuestionOption, { questionId: id });
        if (options.length) {
          await manager.save(
            options.map((option, index) =>
              manager.create(QuestionOption, {
                questionId: id,
                optionText: option.optionText,
                isCorrect: !!option.isCorrect,
                orderIndex: option.orderIndex ?? index,
              }),
            ),
          );
        }
      }

      return manager.findOne(Question, {
        where: { id },
        relations: ['options'],
      }) as Promise<Question>;
    });
  }

  async delete(id: number): Promise<boolean> {
    const result = await this.questionsRepo.delete(id);
    return (result.affected || 0) > 0;
  }

  async reorder(items: ReorderItemDto[]): Promise<void> {
    await this.dataSource.transaction(async (manager) => {
      for (const item of items) {
        await manager.update(Question, item.id, { orderIndex: item.orderIndex });
      }
    });
  }

  private async nextOrderIndex(activityId?: number): Promise<number> {
    if (!activityId) return 0;
    const last = await this.questionsRepo.findOne({
      where: { activityId },
      order: { orderIndex: 'DESC' },
    });
    return last ? last.orderIndex + 1 : 0;
  }
}
