import {
  WebSocketGateway,
  WebSocketServer,
  SubscribeMessage,
  OnGatewayConnection,
  OnGatewayDisconnect,
  ConnectedSocket,
  MessageBody,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { Injectable, Logger, Inject, forwardRef } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { PrismaService } from '../../prisma/prisma.service';
import { BattleService } from './battle.service';

interface AuthenticatedSocket extends Socket {
  userId?: string;
  username?: string;
}

export type BattleUpdateType =
  | 'token_added'
  | 'token_removed'
  | 'token_moved'
  | 'token_updated'
  | 'token_hp_changed'
  | 'initiative_set'
  | 'round_advanced'
  | 'session_updated'
  | 'session_deleted';

export interface BattleUpdatePayload {
  sessionId: string;
  type: BattleUpdateType;
  data: any;
}

// CORS origins from environment (fallback to common dev ports)
const getCorsOrigins = () => {
  const origins = process.env.CORS_ORIGINS;
  if (origins) {
    return origins.split(',').map(o => o.trim());
  }
  return ['http://localhost:3000', 'http://localhost:5173', 'http://localhost:5175'];
};

@Injectable()
@WebSocketGateway({
  cors: {
    origin: getCorsOrigins(),
    credentials: true,
  },
  namespace: '/battles',
})
export class BattleGateway implements OnGatewayConnection, OnGatewayDisconnect {
  @WebSocketServer()
  server: Server;

  private logger = new Logger('BattleGateway');
  private connectedClients = new Map<string, Set<string>>(); // sessionId -> Set<socketId>

  constructor(
    private jwtService: JwtService,
    private configService: ConfigService,
    private prisma: PrismaService,
    @Inject(forwardRef(() => BattleService))
    private battleService: BattleService,
  ) {}

  async handleConnection(client: AuthenticatedSocket) {
    try {
      const token = client.handshake.auth.token || client.handshake.headers.authorization?.split(' ')[1];

      if (!token) {
        this.logger.warn(`Client ${client.id} disconnected: No token provided`);
        client.disconnect();
        return;
      }

      const secret = this.configService.get<string>('JWT_SECRET');
      const payload = this.jwtService.verify(token, { secret });

      // Verify user exists
      const user = await this.prisma.user.findUnique({
        where: { id: payload.sub },
        select: { id: true, username: true },
      });

      if (!user) {
        this.logger.warn(`Client ${client.id} disconnected: User not found`);
        client.disconnect();
        return;
      }

      client.userId = user.id;
      client.username = user.username;

      this.logger.log(`Battle client connected: ${client.id} (User: ${user.username})`);
    } catch (error) {
      this.logger.warn(`Client ${client.id} disconnected: Invalid token`);
      client.disconnect();
    }
  }

  handleDisconnect(client: AuthenticatedSocket) {
    // Remove client from all battle rooms
    this.connectedClients.forEach((clients, sessionId) => {
      clients.delete(client.id);
      if (clients.size === 0) {
        this.connectedClients.delete(sessionId);
      }
    });

    this.logger.log(`Battle client disconnected: ${client.id}`);
  }

  @SubscribeMessage('join_battle')
  async handleJoinBattle(
    @ConnectedSocket() client: AuthenticatedSocket,
    @MessageBody() data: { sessionId: string },
  ) {
    if (!client.userId) {
      return { success: false, error: 'Not authenticated' };
    }

    try {
      // Verify user has access to this battle session
      const session = await this.prisma.battleSession.findUnique({
        where: { id: data.sessionId },
        include: {
          campaign: {
            include: { members: true }
          }
        },
      });

      if (!session) {
        return { success: false, error: 'Battle session not found' };
      }

      const isGM = session.campaign.gmId === client.userId;
      const isMember = session.campaign.members.some(m => m.userId === client.userId);

      if (!isGM && !isMember) {
        return { success: false, error: 'No access to this battle' };
      }

      // Join the room
      const room = `battle:${data.sessionId}`;
      client.join(room);

      // Track connected clients
      if (!this.connectedClients.has(data.sessionId)) {
        this.connectedClients.set(data.sessionId, new Set());
      }
      this.connectedClients.get(data.sessionId)?.add(client.id);

      this.logger.log(`Client ${client.id} joined battle room: ${data.sessionId} (isGM: ${isGM})`);

      return { success: true, isGM };
    } catch (error) {
      this.logger.error(`Error joining battle room: ${error}`);
      return { success: false, error: 'Failed to join battle room' };
    }
  }

  @SubscribeMessage('leave_battle')
  handleLeaveBattle(
    @ConnectedSocket() client: AuthenticatedSocket,
    @MessageBody() data: { sessionId: string },
  ) {
    const room = `battle:${data.sessionId}`;
    client.leave(room);

    this.connectedClients.get(data.sessionId)?.delete(client.id);
    if (this.connectedClients.get(data.sessionId)?.size === 0) {
      this.connectedClients.delete(data.sessionId);
    }

    this.logger.log(`Client ${client.id} left battle room: ${data.sessionId}`);

    return { success: true };
  }

  @SubscribeMessage('move_token')
  async handleMoveToken(
    @ConnectedSocket() client: AuthenticatedSocket,
    @MessageBody() data: { sessionId: string; tokenId: string; positionX: number; positionY: number },
  ) {
    if (!client.userId) {
      return { success: false, error: 'Not authenticated' };
    }

    try {
      // Get session to verify campaign
      const session = await this.prisma.battleSession.findUnique({
        where: { id: data.sessionId },
        select: { campaignId: true },
      });

      if (!session) {
        return { success: false, error: 'Session not found' };
      }

      // Move the token via service (this validates GM access)
      const token = await this.battleService.moveToken(
        session.campaignId,
        data.sessionId,
        data.tokenId,
        data.positionX,
        data.positionY,
        client.userId,
      );

      // Broadcast to all clients in the room
      this.broadcastBattleUpdate(data.sessionId, {
        sessionId: data.sessionId,
        type: 'token_moved',
        data: {
          tokenId: data.tokenId,
          positionX: data.positionX,
          positionY: data.positionY,
        },
      });

      return { success: true, token };
    } catch (error) {
      this.logger.error(`Error moving token: ${error}`);
      return { success: false, error: error instanceof Error ? error.message : 'Failed to move token' };
    }
  }

  @SubscribeMessage('update_token_hp')
  async handleUpdateTokenHp(
    @ConnectedSocket() client: AuthenticatedSocket,
    @MessageBody() data: { sessionId: string; tokenId: string; hpCurrent: number },
  ) {
    if (!client.userId) {
      return { success: false, error: 'Not authenticated' };
    }

    try {
      const session = await this.prisma.battleSession.findUnique({
        where: { id: data.sessionId },
        select: { campaignId: true },
      });

      if (!session) {
        return { success: false, error: 'Session not found' };
      }

      const token = await this.battleService.updateToken(
        session.campaignId,
        data.sessionId,
        data.tokenId,
        { hpCurrent: data.hpCurrent },
        client.userId,
      );

      // Broadcast to all clients
      this.broadcastBattleUpdate(data.sessionId, {
        sessionId: data.sessionId,
        type: 'token_hp_changed',
        data: {
          tokenId: data.tokenId,
          hpCurrent: data.hpCurrent,
          hpMax: token.hpMax,
        },
      });

      return { success: true, token };
    } catch (error) {
      this.logger.error(`Error updating token HP: ${error}`);
      return { success: false, error: error instanceof Error ? error.message : 'Failed to update HP' };
    }
  }

  @SubscribeMessage('set_initiative')
  async handleSetInitiative(
    @ConnectedSocket() client: AuthenticatedSocket,
    @MessageBody() data: { sessionId: string; tokenId: string; initiative: number },
  ) {
    if (!client.userId) {
      return { success: false, error: 'Not authenticated' };
    }

    try {
      const session = await this.prisma.battleSession.findUnique({
        where: { id: data.sessionId },
        select: { campaignId: true },
      });

      if (!session) {
        return { success: false, error: 'Session not found' };
      }

      const token = await this.battleService.setInitiative(
        session.campaignId,
        data.sessionId,
        data.tokenId,
        data.initiative,
        client.userId,
      );

      // Broadcast to all clients
      this.broadcastBattleUpdate(data.sessionId, {
        sessionId: data.sessionId,
        type: 'initiative_set',
        data: {
          tokenId: data.tokenId,
          initiative: data.initiative,
        },
      });

      return { success: true, token };
    } catch (error) {
      this.logger.error(`Error setting initiative: ${error}`);
      return { success: false, error: error instanceof Error ? error.message : 'Failed to set initiative' };
    }
  }

  @SubscribeMessage('advance_round')
  async handleAdvanceRound(
    @ConnectedSocket() client: AuthenticatedSocket,
    @MessageBody() data: { sessionId: string },
  ) {
    if (!client.userId) {
      return { success: false, error: 'Not authenticated' };
    }

    try {
      const session = await this.prisma.battleSession.findUnique({
        where: { id: data.sessionId },
        select: { campaignId: true },
      });

      if (!session) {
        return { success: false, error: 'Session not found' };
      }

      const updatedSession = await this.battleService.advanceRound(
        session.campaignId,
        data.sessionId,
        client.userId,
      );

      // Broadcast to all clients
      this.broadcastBattleUpdate(data.sessionId, {
        sessionId: data.sessionId,
        type: 'round_advanced',
        data: {
          roundNumber: updatedSession.roundNumber,
        },
      });

      return { success: true, session: updatedSession };
    } catch (error) {
      this.logger.error(`Error advancing round: ${error}`);
      return { success: false, error: error instanceof Error ? error.message : 'Failed to advance round' };
    }
  }

  // Broadcast battle update to all clients in the room
  broadcastBattleUpdate(sessionId: string, update: BattleUpdatePayload) {
    const room = `battle:${sessionId}`;
    this.server.to(room).emit('battle_update', update);
    this.logger.debug(`Broadcast to ${room}: ${update.type}`);
  }

  // Get number of connected clients for a battle session
  getConnectedClientsCount(sessionId: string): number {
    return this.connectedClients.get(sessionId)?.size || 0;
  }
}
