import {
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Param,
  Body,
  Headers,
  HttpCode,
  HttpStatus,
  UnauthorizedException,
  BadRequestException,
} from '@nestjs/common';
import * as jwt from 'jsonwebtoken';
import { McpServerConfigRepository } from '../../infrastructure/database/postgres/repositories/mcp-server-config.repository';
import { McpClientService } from '../../infrastructure/agents/mcp/mcp-client.service';
import {
  McpServerConfig,
  McpConnectionState,
} from '../../infrastructure/agents/mcp/mcp.types';

interface AdminPayload {
  id: string;
  username: string;
  role: string;
}

interface CreateMcpServerDto {
  serverId: string;
  name: string;
  description?: string;
  transport: 'stdio' | 'sse';
  command?: string;
  args?: string[];
  env?: Record<string, string>;
  url?: string;
  toolsConcurrencySafe?: boolean;
  connectTimeoutMs?: number;
  toolTimeoutMs?: number;
  enabled?: boolean;
}

interface UpdateMcpServerDto {
  name?: string;
  description?: string;
  transport?: 'stdio' | 'sse';
  command?: string;
  args?: string[];
  env?: Record<string, string>;
  url?: string;
  toolsConcurrencySafe?: boolean;
  connectTimeoutMs?: number;
  toolTimeoutMs?: number;
  enabled?: boolean;
}

/**
 * MCP Server 管理 API — 供 admin-client 使用
 */
@Controller('conversations/admin/mcp')
export class AdminMcpController {
  constructor(
    private readonly configRepo: McpServerConfigRepository,
    private readonly mcpClient: McpClientService,
  ) {}

  private verifyAdmin(authorization: string): AdminPayload {
    const token = authorization?.replace('Bearer ', '');
    if (!token) {
      throw new UnauthorizedException('Missing token');
    }
    try {
      const secret = process.env.JWT_SECRET || 'your-jwt-secret-key';
      return jwt.verify(token, secret) as AdminPayload;
    } catch {
      throw new UnauthorizedException('Invalid token');
    }
  }

  private validateTransportConfig(dto: { transport?: string; command?: string; url?: string }) {
    if (dto.transport === 'stdio' && !dto.command) {
      throw new BadRequestException('stdio transport requires "command"');
    }
    if (dto.transport === 'sse' && !dto.url) {
      throw new BadRequestException('sse transport requires "url"');
    }
  }

  // GET /conversations/admin/mcp/overview
  @Get('overview')
  async getOverview(@Headers('authorization') auth: string) {
    this.verifyAdmin(auth);

    const serverStates = this.mcpClient.getServerStates();
    const connectedCount = serverStates.filter(s => s.state === McpConnectionState.CONNECTED).length;
    const errorCount = serverStates.filter(s => s.state === McpConnectionState.ERROR).length;
    const totalTools = serverStates.reduce((sum, s) => sum + s.toolCount, 0);

    return {
      success: true,
      data: {
        totalServers: serverStates.length,
        connectedCount,
        disconnectedCount: serverStates.length - connectedCount - errorCount,
        errorCount,
        totalTools,
      },
    };
  }

  // GET /conversations/admin/mcp/servers
  @Get('servers')
  async listServers(@Headers('authorization') auth: string) {
    this.verifyAdmin(auth);

    const dbConfigs = await this.configRepo.findAll(null);
    const serverStates = this.mcpClient.getServerStates();
    const stateMap = new Map(serverStates.map(s => [s.id, s]));

    return {
      success: true,
      data: dbConfigs.map(config => {
        const state = stateMap.get(config.serverId);
        return {
          id: config.id,
          serverId: config.serverId,
          name: config.name,
          description: config.description,
          transport: config.transport,
          enabled: config.enabled,
          connectionState: state?.state || McpConnectionState.DISCONNECTED,
          toolCount: state?.toolCount || 0,
          lastError: state?.lastError,
          createdAt: config.createdAt,
          updatedAt: config.updatedAt,
        };
      }),
    };
  }

  // GET /conversations/admin/mcp/servers/:id
  @Get('servers/:id')
  async getServer(
    @Headers('authorization') auth: string,
    @Param('id') id: string,
  ) {
    this.verifyAdmin(auth);

    const config = await this.configRepo.findById(id);
    if (!config) {
      return { success: false, error: 'Server not found' };
    }

    const runtimeState = this.mcpClient.getServerState(config.serverId);

    return {
      success: true,
      data: {
        id: config.id,
        serverId: config.serverId,
        name: config.name,
        description: config.description,
        transport: config.transport,
        command: config.command,
        args: config.args,
        env: config.env,
        url: config.url,
        toolsConcurrencySafe: config.toolsConcurrencySafe,
        connectTimeoutMs: config.connectTimeoutMs,
        toolTimeoutMs: config.toolTimeoutMs,
        enabled: config.enabled,
        connectionState: runtimeState?.state || McpConnectionState.DISCONNECTED,
        toolCount: runtimeState?.tools.length || 0,
        tools: runtimeState?.tools || [],
        lastError: runtimeState?.lastError,
        connectedAt: runtimeState?.connectedAt,
        createdAt: config.createdAt,
        updatedAt: config.updatedAt,
      },
    };
  }

  // POST /conversations/admin/mcp/servers
  @Post('servers')
  @HttpCode(HttpStatus.CREATED)
  async createServer(
    @Headers('authorization') auth: string,
    @Body() dto: CreateMcpServerDto,
  ) {
    const admin = this.verifyAdmin(auth);
    this.validateTransportConfig(dto);

    if (!dto.serverId || !dto.name || !dto.transport) {
      throw new BadRequestException('serverId, name, and transport are required');
    }

    const existing = await this.configRepo.findByServerId(dto.serverId, null);
    if (existing) {
      throw new BadRequestException(`Server with ID "${dto.serverId}" already exists`);
    }

    const created = await this.configRepo.create({
      tenantId: null,
      serverId: dto.serverId,
      name: dto.name,
      description: dto.description || null,
      transport: dto.transport,
      command: dto.command || null,
      args: dto.args || null,
      env: dto.env || null,
      url: dto.url || null,
      toolsConcurrencySafe: dto.toolsConcurrencySafe ?? true,
      connectTimeoutMs: dto.connectTimeoutMs ?? 10000,
      toolTimeoutMs: dto.toolTimeoutMs ?? 30000,
      enabled: dto.enabled ?? true,
      createdBy: admin.id,
    });

    // Auto-connect if enabled
    if (created.enabled) {
      try {
        const mcpConfig = this.configRepo.toMcpServerConfig(created);
        await this.mcpClient.addAndConnectServer(mcpConfig);
      } catch {
        // Connection failure is non-fatal — server is saved, can retry later
      }
    }

    return { success: true, data: created };
  }

  // PUT /conversations/admin/mcp/servers/:id
  @Put('servers/:id')
  async updateServer(
    @Headers('authorization') auth: string,
    @Param('id') id: string,
    @Body() dto: UpdateMcpServerDto,
  ) {
    const admin = this.verifyAdmin(auth);

    const existing = await this.configRepo.findById(id);
    if (!existing) {
      return { success: false, error: 'Server not found' };
    }

    if (dto.transport) {
      this.validateTransportConfig({
        transport: dto.transport,
        command: dto.command ?? existing.command ?? undefined,
        url: dto.url ?? existing.url ?? undefined,
      });
    }

    const updated = await this.configRepo.update(id, {
      ...dto,
      updatedBy: admin.id,
    } as any);

    if (!updated) {
      return { success: false, error: 'Update failed' };
    }

    // Reconnect or disconnect based on enabled state
    if (updated.enabled) {
      try {
        const mcpConfig = this.configRepo.toMcpServerConfig(updated);
        await this.mcpClient.reconnectServer(mcpConfig);
      } catch {
        // Non-fatal
      }
    } else {
      await this.mcpClient.removeServer(updated.serverId);
    }

    return { success: true, data: updated };
  }

  // DELETE /conversations/admin/mcp/servers/:id
  @Delete('servers/:id')
  async deleteServer(
    @Headers('authorization') auth: string,
    @Param('id') id: string,
  ) {
    this.verifyAdmin(auth);

    const config = await this.configRepo.findById(id);
    if (!config) {
      return { success: false, error: 'Server not found' };
    }

    await this.mcpClient.removeServer(config.serverId);
    await this.configRepo.remove(id);

    return { success: true };
  }

  // POST /conversations/admin/mcp/servers/:id/connect
  @Post('servers/:id/connect')
  async connectServer(
    @Headers('authorization') auth: string,
    @Param('id') id: string,
  ) {
    this.verifyAdmin(auth);

    const config = await this.configRepo.findById(id);
    if (!config) {
      return { success: false, error: 'Server not found' };
    }

    const mcpConfig = this.configRepo.toMcpServerConfig(config);
    await this.mcpClient.reconnectServer(mcpConfig);

    const state = this.mcpClient.getServerState(config.serverId);
    return {
      success: true,
      data: {
        connectionState: state?.state || McpConnectionState.DISCONNECTED,
        toolCount: state?.tools.length || 0,
        lastError: state?.lastError,
      },
    };
  }

  // POST /conversations/admin/mcp/servers/:id/disconnect
  @Post('servers/:id/disconnect')
  async disconnectServer(
    @Headers('authorization') auth: string,
    @Param('id') id: string,
  ) {
    this.verifyAdmin(auth);

    const config = await this.configRepo.findById(id);
    if (!config) {
      return { success: false, error: 'Server not found' };
    }

    await this.mcpClient.removeServer(config.serverId);
    return { success: true };
  }

  // GET /conversations/admin/mcp/servers/:id/tools
  @Get('servers/:id/tools')
  async getServerTools(
    @Headers('authorization') auth: string,
    @Param('id') id: string,
  ) {
    this.verifyAdmin(auth);

    const config = await this.configRepo.findById(id);
    if (!config) {
      return { success: false, error: 'Server not found' };
    }

    const state = this.mcpClient.getServerState(config.serverId);
    return {
      success: true,
      data: state?.tools || [],
    };
  }

  // POST /conversations/admin/mcp/servers/:id/test
  @Post('servers/:id/test')
  async testServer(
    @Headers('authorization') auth: string,
    @Param('id') id: string,
  ) {
    this.verifyAdmin(auth);

    const config = await this.configRepo.findById(id);
    if (!config) {
      return { success: false, error: 'Server not found' };
    }

    const mcpConfig = this.configRepo.toMcpServerConfig(config);
    const result = await this.mcpClient.testConnection(mcpConfig);
    return { success: true, data: result };
  }

  // POST /conversations/admin/mcp/test-config
  @Post('test-config')
  async testConfig(
    @Headers('authorization') auth: string,
    @Body() dto: CreateMcpServerDto,
  ) {
    this.verifyAdmin(auth);
    this.validateTransportConfig(dto);

    const mcpConfig: McpServerConfig = {
      id: dto.serverId || 'test',
      name: dto.name || 'Test',
      transport: dto.transport,
      command: dto.command,
      args: dto.args,
      env: dto.env,
      url: dto.url,
      toolsConcurrencySafe: dto.toolsConcurrencySafe ?? true,
      connectTimeoutMs: dto.connectTimeoutMs ?? 10000,
      toolTimeoutMs: dto.toolTimeoutMs ?? 30000,
    };

    const result = await this.mcpClient.testConnection(mcpConfig);
    return { success: true, data: result };
  }
}
