import { Injectable, NotFoundException, ForbiddenException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { CreateBattleSessionDto, UpdateBattleSessionDto, CreateBattleTokenDto, UpdateBattleTokenDto } from './dto';

@Injectable()
export class BattleService {
  constructor(private prisma: PrismaService) {}

  // ==========================================
  // BATTLE SESSIONS
  // ==========================================

  async findAllSessions(campaignId: string, userId: string) {
    await this.verifyAccess(campaignId, userId);

    return this.prisma.battleSession.findMany({
      where: { campaignId },
      include: {
        map: true,
        tokens: {
          include: {
            combatant: true,
            character: true,
          },
        },
      },
      orderBy: { createdAt: 'desc' },
    });
  }

  async findSession(campaignId: string, sessionId: string, userId: string) {
    await this.verifyAccess(campaignId, userId);

    const session = await this.prisma.battleSession.findFirst({
      where: { id: sessionId, campaignId },
      include: {
        map: true,
        tokens: {
          include: {
            combatant: {
              include: { abilities: true },
            },
            character: {
              include: {
                abilities: true,
                conditions: true,
              },
            },
          },
          orderBy: [
            { initiative: 'desc' },
            { name: 'asc' },
          ],
        },
      },
    });

    if (!session) {
      throw new NotFoundException('Battle session not found');
    }

    return session;
  }

  async createSession(campaignId: string, dto: CreateBattleSessionDto, userId: string) {
    await this.verifyGMAccess(campaignId, userId);

    // Verify map exists if provided
    if (dto.mapId) {
      const map = await this.prisma.battleMap.findFirst({
        where: { id: dto.mapId, campaignId },
      });
      if (!map) {
        throw new NotFoundException('Battle map not found');
      }
    }

    return this.prisma.battleSession.create({
      data: {
        campaignId,
        name: dto.name,
        mapId: dto.mapId,
        isActive: dto.isActive ?? false,
      },
      include: {
        map: true,
        tokens: true,
      },
    });
  }

  async updateSession(campaignId: string, sessionId: string, dto: UpdateBattleSessionDto, userId: string) {
    await this.verifyGMAccess(campaignId, userId);

    const session = await this.prisma.battleSession.findFirst({
      where: { id: sessionId, campaignId },
    });

    if (!session) {
      throw new NotFoundException('Battle session not found');
    }

    // Verify map exists if changing
    if (dto.mapId) {
      const map = await this.prisma.battleMap.findFirst({
        where: { id: dto.mapId, campaignId },
      });
      if (!map) {
        throw new NotFoundException('Battle map not found');
      }
    }

    return this.prisma.battleSession.update({
      where: { id: sessionId },
      data: {
        name: dto.name,
        mapId: dto.mapId,
        isActive: dto.isActive,
        roundNumber: dto.roundNumber,
      },
      include: {
        map: true,
        tokens: {
          include: {
            combatant: true,
            character: true,
          },
        },
      },
    });
  }

  async deleteSession(campaignId: string, sessionId: string, userId: string) {
    await this.verifyGMAccess(campaignId, userId);

    const session = await this.prisma.battleSession.findFirst({
      where: { id: sessionId, campaignId },
    });

    if (!session) {
      throw new NotFoundException('Battle session not found');
    }

    await this.prisma.battleSession.delete({
      where: { id: sessionId },
    });

    return { message: 'Battle session deleted' };
  }

  // ==========================================
  // BATTLE TOKENS
  // ==========================================

  async addToken(campaignId: string, sessionId: string, dto: CreateBattleTokenDto, userId: string) {
    await this.verifyGMAccess(campaignId, userId);

    const session = await this.prisma.battleSession.findFirst({
      where: { id: sessionId, campaignId },
    });

    if (!session) {
      throw new NotFoundException('Battle session not found');
    }

    // Verify combatant or character exists if provided
    if (dto.combatantId) {
      const combatant = await this.prisma.combatant.findFirst({
        where: { id: dto.combatantId, campaignId },
      });
      if (!combatant) {
        throw new NotFoundException('Combatant not found');
      }
    }

    if (dto.characterId) {
      const character = await this.prisma.character.findFirst({
        where: { id: dto.characterId, campaignId },
      });
      if (!character) {
        throw new NotFoundException('Character not found');
      }
    }

    return this.prisma.battleToken.create({
      data: {
        battleSessionId: sessionId,
        combatantId: dto.combatantId,
        characterId: dto.characterId,
        name: dto.name,
        positionX: dto.positionX,
        positionY: dto.positionY,
        hpCurrent: dto.hpCurrent,
        hpMax: dto.hpMax,
        initiative: dto.initiative,
        conditions: dto.conditions ?? [],
        size: dto.size ?? 1,
      },
      include: {
        combatant: {
          include: { abilities: true },
        },
        character: true,
      },
    });
  }

  async updateToken(campaignId: string, sessionId: string, tokenId: string, dto: UpdateBattleTokenDto, userId: string) {
    await this.verifyGMAccess(campaignId, userId);

    const token = await this.prisma.battleToken.findFirst({
      where: { id: tokenId, battleSessionId: sessionId },
      include: { battleSession: true },
    });

    if (!token || token.battleSession.campaignId !== campaignId) {
      throw new NotFoundException('Battle token not found');
    }

    return this.prisma.battleToken.update({
      where: { id: tokenId },
      data: {
        name: dto.name,
        positionX: dto.positionX,
        positionY: dto.positionY,
        hpCurrent: dto.hpCurrent,
        hpMax: dto.hpMax,
        initiative: dto.initiative,
        conditions: dto.conditions,
        size: dto.size,
      },
      include: {
        combatant: {
          include: { abilities: true },
        },
        character: true,
      },
    });
  }

  async removeToken(campaignId: string, sessionId: string, tokenId: string, userId: string) {
    await this.verifyGMAccess(campaignId, userId);

    const token = await this.prisma.battleToken.findFirst({
      where: { id: tokenId, battleSessionId: sessionId },
      include: { battleSession: true },
    });

    if (!token || token.battleSession.campaignId !== campaignId) {
      throw new NotFoundException('Battle token not found');
    }

    await this.prisma.battleToken.delete({
      where: { id: tokenId },
    });

    return { message: 'Token removed' };
  }

  async moveToken(campaignId: string, sessionId: string, tokenId: string, positionX: number, positionY: number, userId: string) {
    await this.verifyGMAccess(campaignId, userId);

    const token = await this.prisma.battleToken.findFirst({
      where: { id: tokenId, battleSessionId: sessionId },
      include: { battleSession: true },
    });

    if (!token || token.battleSession.campaignId !== campaignId) {
      throw new NotFoundException('Battle token not found');
    }

    return this.prisma.battleToken.update({
      where: { id: tokenId },
      data: { positionX, positionY },
      include: {
        combatant: true,
        character: true,
      },
    });
  }

  // ==========================================
  // INITIATIVE
  // ==========================================

  async setInitiative(campaignId: string, sessionId: string, tokenId: string, initiative: number, userId: string) {
    await this.verifyGMAccess(campaignId, userId);

    const token = await this.prisma.battleToken.findFirst({
      where: { id: tokenId, battleSessionId: sessionId },
      include: { battleSession: true },
    });

    if (!token || token.battleSession.campaignId !== campaignId) {
      throw new NotFoundException('Battle token not found');
    }

    return this.prisma.battleToken.update({
      where: { id: tokenId },
      data: { initiative },
      include: {
        combatant: true,
        character: true,
      },
    });
  }

  async advanceRound(campaignId: string, sessionId: string, userId: string) {
    await this.verifyGMAccess(campaignId, userId);

    const session = await this.prisma.battleSession.findFirst({
      where: { id: sessionId, campaignId },
    });

    if (!session) {
      throw new NotFoundException('Battle session not found');
    }

    return this.prisma.battleSession.update({
      where: { id: sessionId },
      data: { roundNumber: session.roundNumber + 1 },
      include: {
        map: true,
        tokens: {
          include: {
            combatant: true,
            character: true,
          },
          orderBy: [
            { initiative: 'desc' },
            { name: 'asc' },
          ],
        },
      },
    });
  }

  // ==========================================
  // ACCESS HELPERS
  // ==========================================

  private async verifyAccess(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 isGM = campaign.gmId === userId;
    const isMember = campaign.members.some(m => m.userId === userId);

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

    return { campaign, isGM };
  }

  private async verifyGMAccess(campaignId: string, userId: string) {
    const campaign = await this.prisma.campaign.findUnique({
      where: { id: campaignId },
    });

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

    if (campaign.gmId !== userId) {
      throw new ForbiddenException('Only the GM can perform this action');
    }

    return campaign;
  }
}
