import {
  Injectable,
  NotFoundException,
  ForbiddenException,
  Inject,
  forwardRef,
} from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { TranslationsService } from '../translations/translations.service';
import { CharactersGateway } from './characters.gateway';
import { TranslationType } from '../../generated/prisma/client.js';
import {
  CreateCharacterDto,
  UpdateCharacterDto,
  CreateAbilityDto,
  CreateSkillDto,
  CreateFeatDto,
  CreateSpellDto,
  CreateItemDto,
  UpdateItemDto,
  CreateConditionDto,
  CreateResourceDto,
  RestPreviewDto,
  RestResultDto,
  ConditionReduced,
} from './dto';

@Injectable()
export class CharactersService {
  constructor(
    private prisma: PrismaService,
    private translationsService: TranslationsService,
    @Inject(forwardRef(() => CharactersGateway))
    private charactersGateway: CharactersGateway,
  ) {}

  // Check if user has access to campaign
  private async checkCampaignAccess(campaignId: string, userId: string) {
    const campaign = await this.prisma.campaign.findUnique({
      where: { id: campaignId },
      include: { members: true },
    });

    if (!campaign) {
      throw new NotFoundException('Campaign not found');
    }

    const hasAccess =
      campaign.gmId === userId ||
      campaign.members.some((m) => m.userId === userId);

    if (!hasAccess) {
      throw new ForbiddenException('No access to this campaign');
    }

    return campaign;
  }

  // Check if user can edit character
  private async checkCharacterAccess(characterId: string, userId: string, requireOwnership = false) {
    const character = await this.prisma.character.findUnique({
      where: { id: characterId },
      include: { campaign: { include: { members: true } } },
    });

    if (!character) {
      throw new NotFoundException('Character not found');
    }

    const isGM = character.campaign.gmId === userId;
    const isOwner = character.ownerId === userId;

    if (requireOwnership && !isOwner && !isGM) {
      throw new ForbiddenException('Only the owner or GM can modify this character');
    }

    const isMember = character.campaign.members.some((m) => m.userId === userId);
    if (!isGM && !isMember) {
      throw new ForbiddenException('No access to this character');
    }

    return character;
  }

  async create(campaignId: string, dto: CreateCharacterDto, userId: string) {
    await this.checkCampaignAccess(campaignId, userId);

    return this.prisma.character.create({
      data: {
        ...dto,
        campaignId,
        ownerId: userId,
        pathbuilderData: dto.pathbuilderData as any,
      },
      include: {
        owner: { select: { id: true, username: true } },
        abilities: true,
        skills: true,
        feats: true,
        spells: true,
        items: true,
        conditions: true,
        resources: true,
      },
    });
  }

  async findAllByCampaign(campaignId: string, userId: string) {
    await this.checkCampaignAccess(campaignId, userId);

    return this.prisma.character.findMany({
      where: { campaignId },
      include: {
        owner: { select: { id: true, username: true } },
      },
      orderBy: { name: 'asc' },
    });
  }

  /**
   * Enrich equipment with German translations from the Translation table
   */
  private async enrichEquipmentWithTranslations(
    equipment: { name: string; summary?: string | null; effect?: string | null } | null | undefined,
  ): Promise<{ nameGerman?: string; summaryGerman?: string; effectGerman?: string }> {
    if (!equipment) {
      return {};
    }
    try {
      const translation = await this.translationsService.getTranslation(
        TranslationType.EQUIPMENT,
        equipment.name,
        equipment.summary || undefined,
        equipment.effect || undefined,
      );
      return {
        nameGerman: translation.germanName,
        summaryGerman: translation.germanDescription,
        effectGerman: translation.germanEffect,
      };
    } catch {
      return {};
    }
  }

  async findOne(id: string, userId: string) {
    const character = await this.checkCharacterAccess(id, userId);

    const fullCharacter = await this.prisma.character.findUnique({
      where: { id },
      include: {
        owner: { select: { id: true, username: true, avatarUrl: true } },
        abilities: true,
        skills: { orderBy: { skillName: 'asc' } },
        feats: { orderBy: [{ level: 'asc' }, { name: 'asc' }] },
        spells: { orderBy: [{ spellLevel: 'asc' }, { name: 'asc' }] },
        items: {
          orderBy: { name: 'asc' },
          include: {
            equipment: true,
          },
        },
        conditions: true,
        resources: true,
        // Alchemy
        alchemyState: true,
        formulas: {
          orderBy: [{ learnedAt: 'asc' }, { name: 'asc' }],
          include: { equipment: true },
        },
        preparedItems: {
          orderBy: { createdAt: 'desc' },
          include: { equipment: true },
        },
      },
    });

    if (!fullCharacter) {
      return null;
    }

    // Enrich items with equipment translations
    const enrichedItems = await Promise.all(
      fullCharacter.items.map(async (item) => {
        const translations = await this.enrichEquipmentWithTranslations(item.equipment);
        return {
          ...item,
          equipment: item.equipment
            ? { ...item.equipment, ...translations }
            : null,
        };
      }),
    );

    // Enrich formulas with equipment translations
    const enrichedFormulas = await Promise.all(
      fullCharacter.formulas.map(async (formula) => {
        const translations = await this.enrichEquipmentWithTranslations(formula.equipment);
        return {
          ...formula,
          equipment: formula.equipment
            ? { ...formula.equipment, ...translations }
            : null,
        };
      }),
    );

    // Enrich prepared items with equipment translations
    const enrichedPreparedItems = await Promise.all(
      fullCharacter.preparedItems.map(async (item) => {
        const translations = await this.enrichEquipmentWithTranslations(item.equipment);
        return {
          ...item,
          equipment: item.equipment
            ? { ...item.equipment, ...translations }
            : null,
        };
      }),
    );

    return {
      ...fullCharacter,
      items: enrichedItems,
      formulas: enrichedFormulas,
      preparedItems: enrichedPreparedItems,
    };
  }

  async update(id: string, dto: UpdateCharacterDto, userId: string) {
    await this.checkCharacterAccess(id, userId, true);

    return this.prisma.character.update({
      where: { id },
      data: {
        ...dto,
        pathbuilderData: dto.pathbuilderData as any,
      },
      include: {
        owner: { select: { id: true, username: true } },
        abilities: true,
        skills: true,
        feats: true,
        spells: true,
        items: true,
        conditions: true,
        resources: true,
      },
    });
  }

  async remove(id: string, userId: string) {
    await this.checkCharacterAccess(id, userId, true);

    await this.prisma.character.delete({ where: { id } });
    return { message: 'Character deleted successfully' };
  }

  // HP Management
  async updateHp(id: string, hpCurrent: number, hpTemp?: number, userId?: string) {
    if (userId) {
      await this.checkCharacterAccess(id, userId, true);
    }

    const result = await this.prisma.character.update({
      where: { id },
      data: {
        hpCurrent: Math.max(0, hpCurrent),
        ...(hpTemp !== undefined && { hpTemp: Math.max(0, hpTemp) }),
      },
    });

    // Broadcast HP update
    this.charactersGateway.broadcastCharacterUpdate(id, {
      characterId: id,
      type: 'hp',
      data: { hpCurrent: result.hpCurrent, hpTemp: result.hpTemp, hpMax: result.hpMax },
    });

    return result;
  }

  // Credits Management
  async updateCredits(id: string, credits: number, userId?: string) {
    if (userId) {
      await this.checkCharacterAccess(id, userId, true);
    }

    const result = await this.prisma.character.update({
      where: { id },
      data: {
        credits: Math.max(0, credits),
      },
    });

    // Broadcast money update
    this.charactersGateway.broadcastCharacterUpdate(id, {
      characterId: id,
      type: 'money',
      data: { credits: result.credits },
    });

    return result;
  }

  // Abilities
  async setAbilities(characterId: string, abilities: CreateAbilityDto[], userId: string) {
    await this.checkCharacterAccess(characterId, userId, true);

    // Delete existing and create new
    await this.prisma.characterAbility.deleteMany({ where: { characterId } });

    return this.prisma.characterAbility.createMany({
      data: abilities.map((a) => ({ ...a, characterId })),
    });
  }

  // Skills
  async setSkills(characterId: string, skills: CreateSkillDto[], userId: string) {
    await this.checkCharacterAccess(characterId, userId, true);

    await this.prisma.characterSkill.deleteMany({ where: { characterId } });

    return this.prisma.characterSkill.createMany({
      data: skills.map((s) => ({ ...s, characterId })),
    });
  }

  async updateSkill(characterId: string, skillName: string, proficiency: string, userId: string) {
    await this.checkCharacterAccess(characterId, userId, true);

    return this.prisma.characterSkill.upsert({
      where: { characterId_skillName: { characterId, skillName } },
      update: { proficiency: proficiency as any },
      create: { characterId, skillName, proficiency: proficiency as any },
    });
  }

  // Feats
  async addFeat(characterId: string, dto: CreateFeatDto, userId: string) {
    await this.checkCharacterAccess(characterId, userId, true);

    let nameGerman = dto.nameGerman;

    // If featId is provided, translate and update the Feat record
    if (dto.featId) {
      const feat = await this.prisma.feat.findUnique({
        where: { id: dto.featId },
      });

      if (feat) {
        // Check if feat needs translation
        if (!feat.nameGerman || !feat.summaryGerman) {
          const translation = await this.translationsService.getTranslation(
            TranslationType.FEAT,
            feat.name,
            feat.summary || undefined,
          );

          // Update the Feat record with translations
          await this.prisma.feat.update({
            where: { id: dto.featId },
            data: {
              nameGerman: feat.nameGerman || translation.germanName,
              summaryGerman: feat.summaryGerman || translation.germanDescription,
            },
          });

          nameGerman = nameGerman || translation.germanName;
        } else {
          nameGerman = nameGerman || feat.nameGerman;
        }
      }
    }

    // Fallback: translate just the name if still missing
    if (!nameGerman) {
      const translation = await this.translationsService.getTranslation(
        TranslationType.FEAT,
        dto.name,
      );
      nameGerman = translation.germanName;
    }

    return this.prisma.characterFeat.create({
      data: {
        ...dto,
        nameGerman,
        characterId,
      },
    });
  }

  async removeFeat(characterId: string, featId: string, userId: string) {
    await this.checkCharacterAccess(characterId, userId, true);

    await this.prisma.characterFeat.delete({ where: { id: featId } });
    return { message: 'Feat removed' };
  }

  // Spells
  async addSpell(characterId: string, dto: CreateSpellDto, userId: string) {
    await this.checkCharacterAccess(characterId, userId, true);

    return this.prisma.characterSpell.create({
      data: { ...dto, characterId },
    });
  }

  async updateSpell(characterId: string, spellId: string, prepared: boolean, userId: string) {
    await this.checkCharacterAccess(characterId, userId, true);

    return this.prisma.characterSpell.update({
      where: { id: spellId },
      data: { prepared },
    });
  }

  async removeSpell(characterId: string, spellId: string, userId: string) {
    await this.checkCharacterAccess(characterId, userId, true);

    await this.prisma.characterSpell.delete({ where: { id: spellId } });
    return { message: 'Spell removed' };
  }

  // Items
  async addItem(characterId: string, dto: CreateItemDto, userId: string) {
    await this.checkCharacterAccess(characterId, userId, true);

    // Hole Equipment-Details für die Summary (falls equipmentId vorhanden)
    let summary: string | undefined;
    if (dto.equipmentId) {
      const equipment = await this.prisma.equipment.findUnique({
        where: { id: dto.equipmentId },
        select: { summary: true },
      });
      summary = equipment?.summary || undefined;
    }

    // Übersetze den Item-Namen (und Summary), falls noch nicht vorhanden
    let nameGerman = dto.nameGerman;
    if (!nameGerman && dto.name) {
      const translation = await this.translationsService.getTranslation(
        TranslationType.EQUIPMENT,
        dto.name,
        summary, // Summary wird mit übersetzt
      );
      nameGerman = translation.germanName;
    }

    const result = await this.prisma.characterItem.create({
      data: { ...dto, characterId, bulk: dto.bulk as any, nameGerman },
      include: { equipment: true },
    });

    // Broadcast inventory update
    this.charactersGateway.broadcastCharacterUpdate(characterId, {
      characterId,
      type: 'inventory',
      data: { action: 'add', item: result },
    });

    return result;
  }

  async updateItem(characterId: string, itemId: string, data: UpdateItemDto, userId: string) {
    const character = await this.prisma.character.findUnique({
      where: { id: characterId },
      include: { campaign: true },
    });

    if (!character) {
      throw new NotFoundException('Character not found');
    }

    const isGM = character.campaign.gmId === userId;
    const isOwner = character.ownerId === userId;

    if (!isOwner && !isGM) {
      throw new ForbiddenException('Only the owner or GM can modify this character');
    }

    // Separate player-allowed fields from GM-only fields
    const playerFields = {
      quantity: data.quantity,
      equipped: data.equipped,
      invested: data.invested,
      notes: data.notes,
      alias: data.alias,
    };

    const gmFields = {
      customName: data.customName,
      customDamage: data.customDamage,
      customDamageType: data.customDamageType,
      customTraits: data.customTraits,
      customRange: data.customRange,
      customHands: data.customHands,
    };

    // Build update data based on role
    const updateData: any = {};

    // Players can always update player fields
    Object.entries(playerFields).forEach(([key, value]) => {
      if (value !== undefined) {
        updateData[key] = value;
      }
    });

    // Only GM can update GM fields
    if (isGM) {
      Object.entries(gmFields).forEach(([key, value]) => {
        if (value !== undefined) {
          updateData[key] = value;
        }
      });
    }

    const result = await this.prisma.characterItem.update({
      where: { id: itemId },
      data: updateData,
      include: { equipment: true },
    });

    // Broadcast item update (could be equipment status change or other update)
    const updateType = data.equipped !== undefined ? 'equipment_status' : 'item';
    this.charactersGateway.broadcastCharacterUpdate(characterId, {
      characterId,
      type: updateType,
      data: { action: 'update', item: result },
    });

    return result;
  }

  async removeItem(characterId: string, itemId: string, userId: string) {
    await this.checkCharacterAccess(characterId, userId, true);

    await this.prisma.characterItem.delete({ where: { id: itemId } });

    // Broadcast inventory update
    this.charactersGateway.broadcastCharacterUpdate(characterId, {
      characterId,
      type: 'inventory',
      data: { action: 'remove', itemId },
    });

    return { message: 'Item removed' };
  }

  // Conditions
  async addCondition(characterId: string, dto: CreateConditionDto, userId: string) {
    await this.checkCharacterAccess(characterId, userId, true);

    // Übersetze den Condition-Namen, falls noch nicht vorhanden
    let nameGerman = dto.nameGerman;
    if (!nameGerman && dto.name) {
      const translation = await this.translationsService.getTranslation(
        TranslationType.CONDITION,
        dto.name,
      );
      nameGerman = translation.germanName;
    }

    const result = await this.prisma.characterCondition.create({
      data: { ...dto, characterId, nameGerman },
    });

    // Broadcast conditions update
    this.charactersGateway.broadcastCharacterUpdate(characterId, {
      characterId,
      type: 'conditions',
      data: { action: 'add', condition: result },
    });

    return result;
  }

  async updateCondition(characterId: string, conditionId: string, value: number, userId: string) {
    await this.checkCharacterAccess(characterId, userId, true);

    const result = await this.prisma.characterCondition.update({
      where: { id: conditionId },
      data: { value },
    });

    // Broadcast conditions update
    this.charactersGateway.broadcastCharacterUpdate(characterId, {
      characterId,
      type: 'conditions',
      data: { action: 'update', condition: result },
    });

    return result;
  }

  async removeCondition(characterId: string, conditionId: string, userId: string) {
    await this.checkCharacterAccess(characterId, userId, true);

    await this.prisma.characterCondition.delete({ where: { id: conditionId } });

    // Broadcast conditions update
    this.charactersGateway.broadcastCharacterUpdate(characterId, {
      characterId,
      type: 'conditions',
      data: { action: 'remove', conditionId },
    });

    return { message: 'Condition removed' };
  }

  // Resources
  async setResources(characterId: string, resources: CreateResourceDto[], userId: string) {
    await this.checkCharacterAccess(characterId, userId, true);

    await this.prisma.characterResource.deleteMany({ where: { characterId } });

    return this.prisma.characterResource.createMany({
      data: resources.map((r) => ({ ...r, characterId })),
    });
  }

  async updateResource(characterId: string, resourceName: string, current: number, userId: string) {
    await this.checkCharacterAccess(characterId, userId, true);

    return this.prisma.characterResource.update({
      where: { characterId_name: { characterId, name: resourceName } },
      data: { current: Math.max(0, current) },
    });
  }

  // ==========================================
  // REST SYSTEM
  // ==========================================

  /**
   * Preview what will happen when the character rests
   * PF2e Rest Rules:
   * - HP healing: CON modifier × level (minimum 1 × level)
   * - Fatigued condition is removed
   * - Doomed condition is reduced by 1
   * - Drained condition is reduced by 1
   * - All resources are reset to max (spell slots, focus points, etc.)
   * - Alchemy: Infused items expire, vials refill
   */
  async getRestPreview(characterId: string, userId: string): Promise<RestPreviewDto> {
    const character = await this.checkCharacterAccess(characterId, userId);

    // Load all character data
    const fullCharacter = await this.prisma.character.findUnique({
      where: { id: characterId },
      include: {
        abilities: true,
        conditions: true,
        resources: true,
        alchemyState: true,
        preparedItems: true,
      },
    });

    if (!fullCharacter) {
      throw new NotFoundException('Character not found');
    }

    // Calculate CON modifier
    const conAbility = fullCharacter.abilities.find((a) => a.ability === 'CON');
    const conScore = conAbility?.score || 10;
    const conMod = Math.floor((conScore - 10) / 2);

    // HP healing: CON mod × level (min 1 × level)
    const hpToHeal = Math.max(1, conMod) * fullCharacter.level;
    const hpAfterRest = Math.min(fullCharacter.hpMax, fullCharacter.hpCurrent + hpToHeal);

    // Conditions to remove (Fatigued)
    const conditionsToRemove: string[] = [];
    const fatigued = fullCharacter.conditions.find(
      (c) => c.name.toLowerCase() === 'fatigued' || c.nameGerman?.toLowerCase() === 'erschöpft',
    );
    if (fatigued) {
      conditionsToRemove.push(fatigued.nameGerman || fatigued.name);
    }

    // Conditions to reduce (Doomed, Drained)
    const conditionsToReduce: ConditionReduced[] = [];
    for (const condition of fullCharacter.conditions) {
      const nameLower = condition.name.toLowerCase();
      const isDoomed = nameLower === 'doomed' || condition.nameGerman?.toLowerCase() === 'verdammt';
      const isDrained = nameLower === 'drained' || condition.nameGerman?.toLowerCase() === 'entkräftet';

      if ((isDoomed || isDrained) && condition.value && condition.value > 0) {
        conditionsToReduce.push({
          name: condition.nameGerman || condition.name,
          oldValue: condition.value,
          newValue: condition.value - 1,
        });
      }
    }

    // Resources to reset
    const resourcesToReset: string[] = fullCharacter.resources
      .filter((r) => r.current < r.max)
      .map((r) => r.name);

    // Check if character has alchemy to reset
    const alchemyReset = !!fullCharacter.alchemyState;

    // Count infused items that will expire (only items with isInfused=true)
    const infusedItemsCount = fullCharacter.preparedItems
      .filter((item) => item.isInfused)
      .reduce((sum, item) => sum + item.quantity, 0);

    return {
      hpToHeal: hpAfterRest - fullCharacter.hpCurrent,
      hpAfterRest,
      conditionsToRemove,
      conditionsToReduce,
      resourcesToReset,
      alchemyReset,
      infusedItemsCount,
    };
  }

  /**
   * Perform a full rest for the character
   */
  async performRest(characterId: string, userId: string): Promise<RestResultDto> {
    const character = await this.checkCharacterAccess(characterId, userId, true);

    // Load all character data
    const fullCharacter = await this.prisma.character.findUnique({
      where: { id: characterId },
      include: {
        abilities: true,
        conditions: true,
        resources: true,
        alchemyState: true,
        preparedItems: true,
      },
    });

    if (!fullCharacter) {
      throw new NotFoundException('Character not found');
    }

    // Calculate CON modifier
    const conAbility = fullCharacter.abilities.find((a) => a.ability === 'CON');
    const conScore = conAbility?.score || 10;
    const conMod = Math.floor((conScore - 10) / 2);

    // HP healing
    const hpToHeal = Math.max(1, conMod) * fullCharacter.level;
    const newHpCurrent = Math.min(fullCharacter.hpMax, fullCharacter.hpCurrent + hpToHeal);
    const hpHealed = newHpCurrent - fullCharacter.hpCurrent;

    // Track what happened
    const conditionsRemoved: string[] = [];
    const conditionsReduced: ConditionReduced[] = [];
    const resourcesReset: string[] = [];

    // Process conditions
    for (const condition of fullCharacter.conditions) {
      const nameLower = condition.name.toLowerCase();
      const isFatigued =
        nameLower === 'fatigued' || condition.nameGerman?.toLowerCase() === 'erschöpft';
      const isDoomed = nameLower === 'doomed' || condition.nameGerman?.toLowerCase() === 'verdammt';
      const isDrained =
        nameLower === 'drained' || condition.nameGerman?.toLowerCase() === 'entkräftet';

      if (isFatigued) {
        // Remove Fatigued
        await this.prisma.characterCondition.delete({ where: { id: condition.id } });
        conditionsRemoved.push(condition.nameGerman || condition.name);
      } else if ((isDoomed || isDrained) && condition.value && condition.value > 0) {
        const newValue = condition.value - 1;
        if (newValue <= 0) {
          // Remove if reduced to 0
          await this.prisma.characterCondition.delete({ where: { id: condition.id } });
          conditionsRemoved.push(condition.nameGerman || condition.name);
        } else {
          // Reduce by 1
          await this.prisma.characterCondition.update({
            where: { id: condition.id },
            data: { value: newValue },
          });
          conditionsReduced.push({
            name: condition.nameGerman || condition.name,
            oldValue: condition.value,
            newValue,
          });
        }
      }
    }

    // Reset resources to max
    for (const resource of fullCharacter.resources) {
      if (resource.current < resource.max) {
        await this.prisma.characterResource.update({
          where: { id: resource.id },
          data: { current: resource.max },
        });
        resourcesReset.push(resource.name);
      }
    }

    // Update HP
    await this.prisma.character.update({
      where: { id: characterId },
      data: { hpCurrent: newHpCurrent },
    });

    // Reset alchemy if present
    let alchemyReset = false;
    if (fullCharacter.alchemyState) {
      // Delete only infused items (non-infused/permanent items remain)
      await this.prisma.characterPreparedItem.deleteMany({
        where: { characterId, isInfused: true },
      });

      // Reset vials to max and reset batch counter
      await this.prisma.characterAlchemyState.update({
        where: { characterId },
        data: {
          versatileVialsCurrent: fullCharacter.alchemyState.versatileVialsMax,
          advancedAlchemyBatch: 0,
          lastRestAt: new Date(),
        },
      });

      alchemyReset = true;
    }

    // Broadcast the rest event
    this.charactersGateway.broadcastCharacterUpdate(characterId, {
      characterId,
      type: 'rest',
      data: {
        hpCurrent: newHpCurrent,
        hpHealed,
        conditionsRemoved,
        conditionsReduced,
        resourcesReset,
        alchemyReset,
      },
    });

    return {
      hpHealed,
      hpCurrent: newHpCurrent,
      conditionsRemoved,
      conditionsReduced,
      resourcesReset,
      alchemyReset,
    };
  }
}
