import {
  BadRequestException,
  Injectable,
} from '@nestjs/common';

import type { PlayerListResponse } from '@hexahost/contracts';
import type { GameServer } from '@hexahost/database';

import { NodeBridgeService } from '../../nodes/node-bridge.service';
import { PrismaService } from '../../prisma/prisma.service';
import { FilesService } from '../files/files.service';
import { findOwnedServer } from '../shared/server-ownership.util';

const WHITELIST_FILE = 'whitelist.json';
const OPS_FILE = 'ops.json';

@Injectable()
export class PlayersService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly nodeBridge: NodeBridgeService,
    private readonly filesService: FilesService,
  ) {}

  async listPlayers(
    userId: string,
    serverId: string,
  ): Promise<PlayerListResponse> {
    const server = await findOwnedServer(this.prisma, userId, serverId);

    if (!server.nodeId) {
      throw new BadRequestException('Server is not assigned to a node');
    }

    const [online, whitelist, operators] = await Promise.all([
      this.fetchOnlinePlayers(server),
      this.readJsonFile(userId, serverId, WHITELIST_FILE, []),
      this.readJsonFile(userId, serverId, OPS_FILE, []),
    ]);

    return {
      online,
      whitelist,
      operators,
    };
  }

  private async fetchOnlinePlayers(
    server: GameServer,
  ): Promise<PlayerListResponse['online']> {
    if (server.status !== 'RUNNING') {
      return [];
    }

    const response = await this.nodeBridge.sendAgentRequest(
      server.nodeId!,
      'server.command',
      {
        serverId: server.id,
        generation: server.version,
        command: 'list',
      },
      10_000,
    );

    if (!response.success) {
      return [];
    }

    const output =
      typeof response.result === 'object' &&
      response.result !== null &&
      'output' in response.result &&
      typeof (response.result as { output: unknown }).output === 'string'
        ? (response.result as { output: string }).output
        : typeof response.result === 'string'
          ? response.result
          : '';

    return this.parseListCommandOutput(output);
  }

  private parseListCommandOutput(output: string): PlayerListResponse['online'] {
    const match = output.match(/There are \d+ of a max(?:imum)? of \d+ players online:(.*)$/i);
    if (!match?.[1]?.trim()) {
      return [];
    }

    return match[1]
      .split(',')
      .map((name) => name.trim())
      .filter(Boolean)
      .map((name) => ({ name }));
  }

  private async readJsonFile<T>(
    userId: string,
    serverId: string,
    path: string,
    fallback: T,
  ): Promise<T> {
    try {
      const file = await this.filesService.readFile(userId, serverId, path);
      return JSON.parse(file.content) as T;
    } catch {
      return fallback;
    }
  }
}
