import { Injectable, BadRequestException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Character } from '../character/entities/character.entity';
import { Monster } from '../monster/monster.entity';
import { MonsterService } from '../monster/monster.service';
import { CombatLog } from './combat-log.entity';
import { StartCombatDto } from './dto/start-combat.dto';
import { User } from '../user/user.entity';
import { ItemService } from '../item/item.service';
import { MaterialService } from '../material/material.service';
import { CommunityService } from '../community/community.service';
import {
  resolveCombat,
  applyXpGain,
  CombatantStats,
} from './combat.engine';

const COMBAT_ENDURANCE_COST = 10;
const DEFEAT_ENDURANCE_PENALTY = 50;
const DEFEAT_HP_RATIO = 0.2;    // 20% hpMax à la défaite
const VICTORY_HP_REGEN_RATIO = 0.1; // +10% hpMax à la victoire
const DEFEAT_GOLD_LOSS_RATIO = 0.05; // perte 5% or à la défaite

@Injectable()
export class CombatService {
  constructor(
    @InjectRepository(Character)
    private readonly characterRepository: Repository<Character>,
    @InjectRepository(CombatLog)
    private readonly combatLogRepository: Repository<CombatLog>,
    private readonly monsterService: MonsterService,
    private readonly itemService: ItemService,
    private readonly materialService: MaterialService,
    private readonly communityService: CommunityService,
    private readonly eventEmitter: EventEmitter2,
  ) {}

  async startCombat(dto: StartCombatDto, user: User) {
    // Charger le personnage
    const character = await this.characterRepository.findOne({
      where: { userId: user.id },
    });
    if (!character) throw new BadRequestException('Aucun personnage trouvé');

    // Charger le monstre
    const monster = await this.monsterService.findOne(dto.monsterId);

    // Calculer l'endurance actuelle (lazy pattern)
    const elapsedMinutes = (Date.now() - character.lastEnduranceTs.getTime()) / 60_000;
    const recharge = Math.floor(elapsedMinutes / 6);
    const enduranceCurrent = Math.min(character.enduranceSaved + recharge, character.enduranceMax);

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

    if (character.hpCurrent <= 0) {
      throw new BadRequestException('Votre personnage est KO — récupérez d\'abord vos PV');
    }

    // Charger l'équipement actif du personnage
    const equipped = await this.itemService.getEquippedItems(character.id);
    const FORGE_BONUS_PER_LEVEL = 2;
    const weaponAttack = equipped.weapon
      ? equipped.weapon.item.attackBonus + equipped.weapon.forgeLevel * FORGE_BONUS_PER_LEVEL
      : 0;
    const armorDefense = equipped.armor
      ? equipped.armor.item.defenseBonus + equipped.armor.forgeLevel * FORGE_BONUS_PER_LEVEL
      : 0;

    // Construire les stats des combattants
    const playerStats: CombatantStats = {
      name: character.name,
      hpCurrent: character.hpCurrent,
      hpMax: character.hpMax,
      force: character.force,
      agilite: character.agilite,
      intelligence: character.intelligence,
      chance: character.chance,
      attack: weaponAttack,
      defense: armorDefense,
      attackType: dto.attackType,
    };

    const monsterStats: CombatantStats = {
      name: monster.name,
      hpCurrent: monster.hp,
      hpMax: monster.hp,
      force: 0,
      agilite: 0,
      intelligence: 0,
      chance: 0, // pas de crit/esquive pour les monstres Sprint 2
      attack: monster.attack,
      defense: monster.defense,
      attackType: monster.attackType,
    };

    // Résolution combat
    const result = resolveCombat(
      playerStats,
      monsterStats,
      monster.xpReward,
      monster.goldMin,
      monster.goldMax,
    );

    // Appliquer les effets post-combat sur le personnage
    let newHp = character.hpCurrent;
    let newEnduranceSaved = enduranceCurrent - COMBAT_ENDURANCE_COST;
    let goldLost = 0;
    let levelUpData = { levelsGained: 0, statPointsGained: 0, newLevel: character.level, newXp: character.xp };

    if (result.winner === 'player') {
      // Victoire : XP + Or + récup 10% PV
      levelUpData = applyXpGain(character.level, character.xp, result.xpEarned);
      character.xp = levelUpData.newXp;
      character.level = levelUpData.newLevel;
      character.statPoints = (character.statPoints ?? 0) + levelUpData.statPointsGained;
      character.gold += result.goldEarned;
      newHp = Math.min(character.hpMax, character.hpCurrent + Math.floor(character.hpMax * VICTORY_HP_REGEN_RATIO));
    } else {
      // Défaite : retour auberge + pénalités
      newEnduranceSaved = Math.max(0, newEnduranceSaved - DEFEAT_ENDURANCE_PENALTY);
      newHp = Math.max(1, Math.floor(character.hpMax * DEFEAT_HP_RATIO));
      goldLost = Math.floor(character.gold * DEFEAT_GOLD_LOSS_RATIO);
      character.gold = Math.max(0, character.gold - goldLost);
    }

    // Track total gold earned (for achievements)
    if (result.winner === 'player') {
      character.totalGoldEarned = Number(character.totalGoldEarned ?? 0) + result.goldEarned;
    }

    // Sauvegarder l'endurance (lazy reset)
    character.hpCurrent = newHp;
    character.enduranceSaved = newEnduranceSaved;
    character.lastEnduranceTs = new Date();
    await this.characterRepository.save(character);

    // Emit achievement & community events
    if (result.winner === 'player') {
      this.eventEmitter.emit('achievement.check', {
        characterId: character.id,
        type: 'combat_wins',
        increment: 1,
      });
      this.eventEmitter.emit('achievement.check', {
        characterId: character.id,
        type: 'level_reached',
        increment: 0,
        absolute: character.level,
      });
      this.eventEmitter.emit('achievement.check', {
        characterId: character.id,
        type: 'gold_accumulated',
        increment: 0,
        absolute: Number(character.totalGoldEarned),
      });
      this.eventEmitter.emit('community.contribute', {
        characterId: character.id,
        type: 'total_monsters_killed',
        increment: 1,
      });
      this.eventEmitter.emit('community.contribute', {
        characterId: character.id,
        type: 'total_gold_earned',
        increment: result.goldEarned,
      });
    }

    // Apply XP boost from community
    if (result.winner === 'player') {
      const xpBoost = await this.communityService.getActiveMultiplier('xp_boost');
      if (xpBoost > 1.0) {
        const bonusXp = Math.floor(result.xpEarned * (xpBoost - 1));
        if (bonusXp > 0) {
          const boosted = applyXpGain(character.level, character.xp, bonusXp);
          character.xp = boosted.newXp;
          character.level = boosted.newLevel;
          character.statPoints = (character.statPoints ?? 0) + boosted.statPointsGained;
          await this.characterRepository.save(character);
        }
      }
    }

    // Loot matériaux — 40% de chance après victoire si le monstre a un drop_material_id
    let lootMaterial: { name: string; quantity: number } | null = null;
    if (result.winner === 'player' && monster.dropMaterialId && Math.random() < 0.4) {
      await this.materialService.addMaterial(character.id, monster.dropMaterialId, 1);
      lootMaterial = { name: 'matériau', quantity: 1 };
    }

    // Persister le log
    const combatLog = this.combatLogRepository.create({
      characterId: character.id,
      monsterId: monster.id,
      winner: result.winner,
      totalRounds: result.totalRounds,
      roundsData: result.rounds,
      xpEarned: result.xpEarned,
      goldEarned: result.goldEarned,
      levelUp: levelUpData.levelsGained > 0,
    });
    await this.combatLogRepository.save(combatLog);

    // Construire la réponse
    const summaryParts: string[] = [];
    if (result.winner === 'player') {
      summaryParts.push(`Victoire en ${result.totalRounds} tours !`);
      summaryParts.push(`+${result.xpEarned} XP, +${result.goldEarned} Or.`);
      if (levelUpData.levelsGained > 0) {
        summaryParts.push(`LEVEL UP ! Niveau ${levelUpData.newLevel} atteint. +${levelUpData.statPointsGained} points de stats.`);
      }
    } else {
      summaryParts.push(`Défaite au tour ${result.totalRounds}. Retour à l'auberge.`);
      if (goldLost > 0) summaryParts.push(`−${goldLost} Or perdu.`);
    }

    if (lootMaterial) {
      summaryParts.push(`Loot : 1 matériau obtenu !`);
    }

    return {
      winner: result.winner,
      rounds: result.rounds,
      summary: summaryParts.join(' '),
      rewards: {
        xp: result.xpEarned,
        gold: result.goldEarned,
        goldLost,
        levelUp: levelUpData.levelsGained > 0,
        newLevel: levelUpData.newLevel,
        statPointsGained: levelUpData.statPointsGained,
        loot: lootMaterial,
      },
      character: {
        level: character.level,
        xp: character.xp,
        gold: character.gold,
        hpCurrent: character.hpCurrent,
        hpMax: character.hpMax,
        enduranceCurrent: character.enduranceSaved, // déjà le nouveau enduranceSaved post-combat
        enduranceMax: character.enduranceMax,
        statPoints: character.statPoints ?? 0,
      },
    };
  }

  async getHistory(user: User) {
    const character = await this.characterRepository.findOne({
      where: { userId: user.id },
    });
    if (!character) throw new BadRequestException('Aucun personnage trouvé');

    return this.combatLogRepository.find({
      where: { characterId: character.id },
      order: { createdAt: 'DESC' },
      take: 20,
      relations: ['monster'],
      select: {
        id: true,
        winner: true,
        totalRounds: true,
        xpEarned: true,
        goldEarned: true,
        levelUp: true,
        createdAt: true,
        monster: { id: true, name: true, minLevel: true, maxLevel: true } as any,
      },
    });
  }
}
