import { Injectable, BadRequestException, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, Repository, LessThanOrEqual } from 'typeorm';
import { Item } from '../item/item.entity';
import { CharacterItem } from '../item/character-item.entity';
import { Character } from '../character/entities/character.entity';
import { QuestArc } from '../quest/quest-arc.entity';
import { PlayerQuestArc } from '../quest/player-quest-arc.entity';
import { getUnlockedZones } from '../common/zone-access';

const SELL_RATIO = 0.4; // 40% du prix d'achat
const POTION_HEAL_RATIO = 0.5; // 50% HP max

@Injectable()
export class ShopService {
  constructor(
    @InjectRepository(Item)
    private readonly itemRepo: Repository<Item>,
    @InjectRepository(CharacterItem)
    private readonly charItemRepo: Repository<CharacterItem>,
    @InjectRepository(Character)
    private readonly characterRepo: Repository<Character>,
    @InjectRepository(QuestArc)
    private readonly arcRepo: Repository<QuestArc>,
    @InjectRepository(PlayerQuestArc)
    private readonly playerArcRepo: Repository<PlayerQuestArc>,
    private readonly dataSource: DataSource,
  ) {}

  async getCatalogue(characterId: string) {
    const char = await this.characterRepo.findOne({ where: { id: characterId } });
    if (!char) throw new BadRequestException('Aucun personnage');

    const unlockedZones = await getUnlockedZones(char.id, this.arcRepo, this.playerArcRepo);

    const items = await this.itemRepo.find({
      where: { buyPrice: LessThanOrEqual(999999) },
      order: { type: 'ASC', minLevel: 'ASC', buyPrice: 'ASC' },
    });

    return items
      .filter(i => i.buyPrice > 0)
      .filter(i => !i.zone || unlockedZones.includes(i.zone)) // zone null = toujours visible (potions)
      .map(i => ({
        ...i,
        affordable: char.gold >= i.buyPrice,
        levelOk: char.level >= i.minLevel,
        sellPrice: Math.floor(i.buyPrice * SELL_RATIO),
      }));
  }

  async buy(itemId: string, characterId: string) {
    return this.dataSource.transaction(async (manager) => {
      const item = await manager.getRepository(Item).findOne({ where: { id: itemId } });
      if (!item) throw new NotFoundException('Item introuvable');
      if (item.buyPrice <= 0) throw new BadRequestException('Item non achetable');

      const char = await manager
        .getRepository(Character)
        .createQueryBuilder('c')
        .setLock('pessimistic_write')
        .where('c.id = :id', { id: characterId })
        .getOne();
      if (!char) throw new BadRequestException('Aucun personnage');

      if (char.level < item.minLevel) {
        throw new BadRequestException(`Niveau ${item.minLevel} requis`);
      }
      if (char.gold < item.buyPrice) {
        throw new BadRequestException(`Or insuffisant (${char.gold}/${item.buyPrice})`);
      }

      // Consumable = effet immédiat, pas d'inventaire
      if (item.type === 'consumable') {
        // Endurance potion: forceBonus > 0 = endurance restore amount
        const isEndurancePotion = item.forceBonus > 0;

        if (isEndurancePotion) {
          // Calc current endurance (lazy)
          const elapsed = (Date.now() - char.lastEnduranceTs.getTime()) / 60_000;
          const recharge = Math.floor(elapsed / 3);
          const currentEndurance = Math.min(char.enduranceSaved + recharge, char.enduranceMax);

          if (currentEndurance >= char.enduranceMax) {
            throw new BadRequestException('Endurance déjà au maximum');
          }

          char.gold -= item.buyPrice;
          const endBefore = currentEndurance;
          const restored = Math.min(item.forceBonus, char.enduranceMax - currentEndurance);
          char.enduranceSaved = currentEndurance + restored;
          char.lastEnduranceTs = new Date();
          await manager.save(char);

          return {
            bought: true,
            item: item.name,
            type: 'consumable',
            effectType: 'endurance',
            goldSpent: item.buyPrice,
            effect: { enduranceBefore: endBefore, enduranceAfter: char.enduranceSaved, restored },
          };
        }

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

        char.gold -= item.buyPrice;
        const hpBefore = char.hpCurrent;
        char.hpCurrent = Math.min(char.hpMax, char.hpCurrent + Math.floor(char.hpMax * POTION_HEAL_RATIO));
        await manager.save(char);

        return {
          bought: true,
          item: item.name,
          type: 'consumable',
          effectType: 'hp',
          goldSpent: item.buyPrice,
          effect: { hpBefore, hpAfter: char.hpCurrent, healed: char.hpCurrent - hpBefore },
        };
      }

      // Equipment = ajout à l'inventaire
      char.gold -= item.buyPrice;
      await manager.save(char);

      const charItem = manager.getRepository(CharacterItem).create({
        characterId,
        itemId: item.id,
        forgeLevel: 0,
        equipped: false,
      });
      await manager.save(charItem);

      return {
        bought: true,
        item: item.name,
        type: item.type,
        goldSpent: item.buyPrice,
        charItemId: charItem.id,
      };
    });
  }

  async sell(charItemId: string, characterId: string) {
    return this.dataSource.transaction(async (manager) => {
      const charItem = await manager.getRepository(CharacterItem).findOne({
        where: { id: charItemId, characterId },
        relations: ['item'],
      });
      if (!charItem) throw new NotFoundException('Item non trouvé dans l\'inventaire');
      if (charItem.equipped) throw new BadRequestException('Déséquipez l\'item avant de le vendre');

      // Prix de vente = base + investissement forge (coûts cumulés * 50%)
      const FORGE_GOLD_COST: Record<number, number> = { 1: 50, 2: 100, 3: 250, 4: 500, 5: 1000 };
      let forgeInvestment = 0;
      for (let i = 1; i <= charItem.forgeLevel; i++) {
        forgeInvestment += FORGE_GOLD_COST[i] ?? 0;
      }
      const sellPrice = Math.floor(charItem.item.buyPrice * SELL_RATIO + forgeInvestment * 0.5);

      const char = await manager
        .getRepository(Character)
        .createQueryBuilder('c')
        .setLock('pessimistic_write')
        .where('c.id = :id', { id: characterId })
        .getOne();
      if (!char) throw new BadRequestException('Aucun personnage');

      char.gold += sellPrice;
      await manager.save(char);
      await manager.remove(charItem);

      return {
        sold: true,
        item: charItem.item.name,
        goldEarned: sellPrice,
      };
    });
  }
}
