import {
  Injectable,
  ConflictException,
  NotFoundException,
  BadRequestException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, Repository } from 'typeorm';
import { Character } from './entities/character.entity';
import { LevelThreshold } from './entities/level-threshold.entity';
import { CreateCharacterDto } from './dto/create-character.dto';
import { DistributeStatsDto } from './dto/distribute-stats.dto';
import { User } from '../user/user.entity';
import { xpRequiredForLevel } from '../combat/combat.engine';

const STAT_POOL = 10; // 5 stats × 1 base + 5 points à distribuer
const ENDURANCE_REGEN_MINUTES = 3; // 1 pt d'endurance toutes les 3 min = 20 pts/heure
const REST_ENDURANCE_COST = 10;
const REST_HP_REGEN_RATIO = 0.5; // +50% hpMax

@Injectable()
export class CharacterService {
  constructor(
    @InjectRepository(Character)
    private readonly characterRepository: Repository<Character>,
    @InjectRepository(LevelThreshold)
    private readonly levelThresholdRepository: Repository<LevelThreshold>,
    private readonly dataSource: DataSource,
  ) {}

  // Pattern lazy calculation — pas de timer actif
  private calculateEndurance(character: Character): number {
    const elapsedMinutes =
      (Date.now() - character.lastEnduranceTs.getTime()) / 60_000;
    const recharge = Math.floor(elapsedMinutes / ENDURANCE_REGEN_MINUTES);
    return Math.min(character.enduranceSaved + recharge, character.enduranceMax);
  }

  async create(dto: CreateCharacterDto, user: User) {
    const totalStats =
      dto.force + dto.agilite + dto.intelligence + dto.chance + dto.vitalite;

    if (totalStats !== STAT_POOL) {
      throw new BadRequestException(
        `La somme des stats doit être égale à ${STAT_POOL} (reçu : ${totalStats})`,
      );
    }

    const existing = await this.characterRepository.findOne({
      where: { userId: user.id },
    });
    if (existing) {
      throw new ConflictException('Ce joueur possède déjà un personnage');
    }

    const character = this.characterRepository.create({
      userId: user.id,
      name: dto.name,
      force: dto.force,
      agilite: dto.agilite,
      intelligence: dto.intelligence,
      chance: dto.chance,
      vitalite: dto.vitalite,
      enduranceSaved: 100,
      lastEnduranceTs: new Date(),
      enduranceMax: 100,
    });

    const saved = await this.characterRepository.save(character);
    return {
      ...saved,
      enduranceCurrent: this.calculateEndurance(saved),
      xpToNextLevel: xpRequiredForLevel(saved.level),
    };
  }

  async findByUser(user: User) {
    const character = await this.characterRepository.findOne({
      where: { userId: user.id },
    });

    if (!character) {
      throw new NotFoundException('Aucun personnage trouvé pour ce joueur');
    }

    return {
      ...character,
      enduranceCurrent: this.calculateEndurance(character),
      xpToNextLevel: xpRequiredForLevel(character.level),
    };
  }

  async getEndurance(
    user: User,
  ): Promise<{ enduranceCurrent: number; enduranceMax: number; rechargeRatePerHour: number }> {
    const character = await this.characterRepository.findOne({
      where: { userId: user.id },
    });

    if (!character) {
      throw new NotFoundException('Aucun personnage trouvé pour ce joueur');
    }

    return {
      enduranceCurrent: this.calculateEndurance(character),
      enduranceMax: character.enduranceMax,
      rechargeRatePerHour: 60 / ENDURANCE_REGEN_MINUTES,
    };
  }

  async distributeStats(dto: DistributeStatsDto, user: User) {
    return this.dataSource.transaction(async (manager) => {
      const character = await manager
        .getRepository(Character)
        .createQueryBuilder('c')
        .setLock('pessimistic_write')
        .where('c.user_id = :userId', { userId: user.id })
        .getOne();

      if (!character) throw new NotFoundException('Aucun personnage trouvé');

      const totalToDistribute =
        (dto.force ?? 0) + (dto.agilite ?? 0) + (dto.intelligence ?? 0) +
        (dto.chance ?? 0) + (dto.vitalite ?? 0);

      if (totalToDistribute <= 0) {
        throw new BadRequestException('Aucun point à distribuer');
      }

      if (totalToDistribute > (character.statPoints ?? 0)) {
        throw new BadRequestException(
          `Points insuffisants (${character.statPoints ?? 0} disponibles, ${totalToDistribute} demandés)`,
        );
      }

      character.force += dto.force ?? 0;
      character.agilite += dto.agilite ?? 0;
      character.intelligence += dto.intelligence ?? 0;
      character.chance += dto.chance ?? 0;
      character.vitalite += dto.vitalite ?? 0;
      character.statPoints = (character.statPoints ?? 0) - totalToDistribute;

      // Vitalité augmente HP max (+10 par point)
      const vitaliteAdded = dto.vitalite ?? 0;
      if (vitaliteAdded > 0) {
        character.hpMax += vitaliteAdded * 10;
        character.hpCurrent += vitaliteAdded * 10; // bonus immédiat
      }

      await manager.save(character);

      return {
        statPoints: character.statPoints,
        stats: {
          force: character.force,
          agilite: character.agilite,
          intelligence: character.intelligence,
          chance: character.chance,
          vitalite: character.vitalite,
        },
        hpMax: character.hpMax,
      };
    });
  }

  async rest(user: User) {
    return this.dataSource.transaction(async (manager) => {
      const character = await manager
        .getRepository(Character)
        .createQueryBuilder('c')
        .setLock('pessimistic_write')
        .where('c.user_id = :userId', { userId: user.id })
        .getOne();

      if (!character) throw new NotFoundException('Aucun personnage trouvé');

      if (character.hpCurrent >= character.hpMax) {
        throw new BadRequestException('PV déjà au maximum');
      }

      // Calculer endurance
      const elapsedMinutes = (Date.now() - character.lastEnduranceTs.getTime()) / 60_000;
      const recharge = Math.floor(elapsedMinutes / ENDURANCE_REGEN_MINUTES);
      const enduranceCurrent = Math.min(character.enduranceSaved + recharge, character.enduranceMax);

      if (enduranceCurrent < REST_ENDURANCE_COST) {
        throw new BadRequestException(
          `Endurance insuffisante (${enduranceCurrent}/${REST_ENDURANCE_COST} requis)`,
        );
      }

      const hpBefore = character.hpCurrent;
      character.hpCurrent = Math.min(
        character.hpMax,
        character.hpCurrent + Math.floor(character.hpMax * REST_HP_REGEN_RATIO),
      );
      character.enduranceSaved = enduranceCurrent - REST_ENDURANCE_COST;
      character.lastEnduranceTs = new Date();

      await manager.save(character);

      return {
        hpBefore,
        hpAfter: character.hpCurrent,
        hpMax: character.hpMax,
        healed: character.hpCurrent - hpBefore,
        enduranceCurrent: character.enduranceSaved,
        enduranceMax: character.enduranceMax,
      };
    });
  }
}
