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

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

export interface CharacterUpdatePayload {
  characterId: string;
  type: 'hp' | 'conditions' | 'item' | 'inventory' | 'money' | 'level' | 'equipment_status' | 'rest' | 'alchemy_vials' | 'alchemy_formulas' | 'alchemy_prepared' | 'alchemy_state';
  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: '/characters',
})
export class CharactersGateway implements OnGatewayConnection, OnGatewayDisconnect {
  @WebSocketServer()
  server: Server;

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

  constructor(
    private jwtService: JwtService,
    private configService: ConfigService,
    private prisma: PrismaService,
  ) {}

  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(`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 character rooms
    this.connectedClients.forEach((clients, characterId) => {
      clients.delete(client.id);
      if (clients.size === 0) {
        this.connectedClients.delete(characterId);
      }
    });

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

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

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

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

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

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

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

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

      this.logger.log(`Client ${client.id} joined character room: ${data.characterId}`);

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

  @SubscribeMessage('leave_character')
  handleLeaveCharacter(
    @ConnectedSocket() client: AuthenticatedSocket,
    @MessageBody() data: { characterId: string },
  ) {
    const room = `character:${data.characterId}`;
    client.leave(room);

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

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

    return { success: true };
  }

  // Broadcast character update to all clients in the room
  broadcastCharacterUpdate(characterId: string, update: CharacterUpdatePayload) {
    const room = `character:${characterId}`;
    this.server.to(room).emit('character_update', update);
    this.logger.debug(`Broadcast to ${room}: ${update.type}`);
  }

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