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

import type {
  FileContent,
  FileListResponse,
  UpdateFileContent,
} from '@hexahost/contracts';
import type { GameServer } from '@hexahost/database';

import { AuditService } from '../../auth/audit.service';
import { NodeBridgeService } from '../../nodes/node-bridge.service';
import { PrismaService } from '../../prisma/prisma.service';
import { findOwnedServer } from '../shared/server-ownership.util';
import { assertSafeServerPath } from '../shared/server-path.util';

const FILE_ACCESS_STATUSES = new Set<GameServer['status']>(['RUNNING', 'STOPPED']);

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

  async listFiles(
    userId: string,
    serverId: string,
    path: string,
  ): Promise<FileListResponse> {
    const server = await this.requireFileAccess(userId, serverId);
    const safePath = assertSafeServerPath(path);

    const response = await this.nodeBridge.sendAgentRequest(
      server.nodeId!,
      'server.files.list',
      {
        serverId,
        generation: server.version,
        path: safePath,
      },
    );

    if (!response.success) {
      throw new ServiceUnavailableException(
        response.errorMessage ?? 'Failed to list files',
      );
    }

    return response.result as FileListResponse;
  }

  async readFile(
    userId: string,
    serverId: string,
    path: string,
  ): Promise<FileContent> {
    const server = await this.requireFileAccess(userId, serverId);
    const safePath = assertSafeServerPath(path);

    const response = await this.nodeBridge.sendAgentRequest(
      server.nodeId!,
      'server.files.read',
      {
        serverId,
        generation: server.version,
        path: safePath,
      },
    );

    if (!response.success) {
      throw new ServiceUnavailableException(
        response.errorMessage ?? 'Failed to read file',
      );
    }

    return response.result as FileContent;
  }

  async writeFile(
    userId: string,
    serverId: string,
    input: UpdateFileContent,
  ): Promise<FileContent> {
    const server = await this.requireFileAccess(userId, serverId);
    const safePath = assertSafeServerPath(input.path);

    const response = await this.nodeBridge.sendAgentRequest(
      server.nodeId!,
      'server.files.write',
      {
        serverId,
        generation: server.version,
        path: safePath,
        content: input.content,
        encoding: input.encoding,
      },
    );

    if (!response.success) {
      throw new ServiceUnavailableException(
        response.errorMessage ?? 'Failed to write file',
      );
    }

    await this.auditService.record({
      action: 'server.file.write',
      userId,
      entityType: 'game_server',
      entityId: serverId,
      metadata: { path: safePath },
    });

    return response.result as FileContent;
  }

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

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

    if (!FILE_ACCESS_STATUSES.has(server.status)) {
      throw new BadRequestException(
        `File access is not available while server is ${server.status}`,
      );
    }

    return server;
  }
}
