import {
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Body,
  Param,
  NotFoundException,
  BadRequestException,
} from '@nestjs/common';
import * as crypto from 'crypto';
import { AgentInstanceRepository } from '../../../infrastructure/repositories/agent-instance.repository';
import { AgentInstanceDeployService } from '../../../infrastructure/services/agent-instance-deploy.service';
import { AgentInstance } from '../../../domain/entities/agent-instance.entity';
import { ConfigService } from '@nestjs/config';

@Controller('api/v1/agent/instances')
export class AgentInstanceController {
  private readonly claudeApiKey: string;

  constructor(
    private readonly instanceRepo: AgentInstanceRepository,
    private readonly deployService: AgentInstanceDeployService,
    private readonly configService: ConfigService,
  ) {
    this.claudeApiKey = this.configService.get<string>('ANTHROPIC_API_KEY', '');
  }

  @Get()
  async list() {
    return this.instanceRepo.findAll();
  }

  @Get(':id')
  async getOne(@Param('id') id: string) {
    const inst = await this.instanceRepo.findById(id);
    if (!inst) throw new NotFoundException(`Instance ${id} not found`);
    return this.sanitize(inst);
  }

  @Post()
  async create(@Body() body: {
    name: string;
    agentType?: string;
    userId: string;
    // Pool-server deployment (no server creds needed)
    usePool?: boolean;
    // User-owned server deployment
    serverHost?: string;
    sshPort?: number;
    sshUser?: string;
    sshKey?: string;   // plaintext private key, only used if usePool=false
    // Optional: DingTalk channel binding
    dingTalkClientId?: string;
    dingTalkClientSecret?: string;
  }) {
    if (!body.name || !body.userId) {
      throw new BadRequestException('name and userId are required');
    }

    const instance = new AgentInstance();
    instance.id = crypto.randomUUID();
    instance.userId = body.userId;
    instance.name = body.name;
    instance.agentType = body.agentType ?? 'openclaw';
    instance.containerName = `openclaw-${instance.id.slice(0, 8)}`;
    instance.status = 'deploying';
    instance.config = {};
    instance.hostPort = 0; // Will be set by deploy service

    const dtClientId = body.dingTalkClientId;
    const dtClientSecret = body.dingTalkClientSecret;

    if (body.usePool !== false) {
      await this.instanceRepo.save(instance);
      this.deployService.deployFromPool(instance, this.claudeApiKey, dtClientId, dtClientSecret)
        .then(() => this.instanceRepo.save(instance))
        .catch(async (err) => {
          instance.status = 'error';
          instance.errorMessage = err.message;
          await this.instanceRepo.save(instance);
        });
    } else {
      if (!body.serverHost || !body.sshUser || !body.sshKey) {
        throw new BadRequestException('serverHost, sshUser, sshKey required for user-owned server');
      }
      instance.serverHost = body.serverHost;
      instance.sshPort = body.sshPort ?? 22;
      instance.sshUser = body.sshUser;
      await this.instanceRepo.save(instance);
      this.deployService.deployToUserServer(instance, body.sshKey, this.claudeApiKey, dtClientId, dtClientSecret)
        .then(() => this.instanceRepo.save(instance))
        .catch(async (err) => {
          instance.status = 'error';
          instance.errorMessage = err.message;
          await this.instanceRepo.save(instance);
        });
    }

    return this.sanitize(instance);
  }

  @Put(':id/stop')
  async stop(@Param('id') id: string) {
    const inst = await this.instanceRepo.findById(id);
    if (!inst) throw new NotFoundException(`Instance ${id} not found`);
    await this.deployService.stopInstance(inst);
    inst.status = 'stopped';
    return this.sanitize(await this.instanceRepo.save(inst));
  }

  @Put(':id/name')
  async rename(@Param('id') id: string, @Body() body: { name: string }) {
    const inst = await this.instanceRepo.findById(id);
    if (!inst) throw new NotFoundException(`Instance ${id} not found`);
    inst.name = body.name;
    return this.sanitize(await this.instanceRepo.save(inst));
  }


  // Called by it0-bridge every 60s to confirm instance is healthy
  @Post(':id/heartbeat')
  async heartbeat(
    @Param('id') id: string,
    @Body() body: { gatewayConnected: boolean; uptime: number },
  ) {
    const inst = await this.instanceRepo.findById(id);
    if (!inst) return { ok: false };
    if (body.gatewayConnected && inst.status !== 'running') {
      inst.status = 'running';
      await this.instanceRepo.save(inst);
    }
    return { ok: true };
  }

  @Delete(':id')
  async remove(@Param('id') id: string) {
    const inst = await this.instanceRepo.findById(id);
    if (!inst) throw new NotFoundException(`Instance ${id} not found`);
    await this.deployService.removeInstance(inst);
    inst.status = 'removed';
    await this.instanceRepo.save(inst);
    return { message: 'Instance removed', id };
  }

  private sanitize(inst: AgentInstance) {
    const { openclawToken, openclawTokenIv, ...safe } = inst;
    return { ...safe, hasToken: !!openclawToken };
  }
}
