import {
  Body,
  Controller,
  Delete,
  ForbiddenException,
  Get,
  HttpCode,
  HttpStatus,
  Inject,
  NotFoundException,
  Param,
  Patch,
  Post,
  UseGuards,
} from '@nestjs/common';
import type { Brain } from '@mosaicstack/brain';
import { BRAIN } from '../brain/brain.tokens.js';
import { AuthGuard } from '../auth/auth.guard.js';
import { CurrentUser } from '../auth/current-user.decorator.js';
import { CreateAgentConfigDto, UpdateAgentConfigDto } from './agent-config.dto.js';

// ─── M4-011 helpers ──────────────────────────────────────────────────────────

type CapabilityFields = {
  domains?: string[] | null;
  preferredModel?: string | null;
  preferredProvider?: string | null;
  toolSets?: string[] | null;
};

/** Extract capability shorthand fields from the DTO (undefined if none provided). */
function buildCapabilities(dto: CapabilityFields): Record<string, unknown> | undefined {
  const hasAny =
    dto.domains !== undefined ||
    dto.preferredModel !== undefined ||
    dto.preferredProvider !== undefined ||
    dto.toolSets !== undefined;

  if (!hasAny) return undefined;

  const cap: Record<string, unknown> = {};
  if (dto.domains !== undefined) cap['domains'] = dto.domains;
  if (dto.preferredModel !== undefined) cap['preferredModel'] = dto.preferredModel;
  if (dto.preferredProvider !== undefined) cap['preferredProvider'] = dto.preferredProvider;
  if (dto.toolSets !== undefined) cap['toolSets'] = dto.toolSets;
  return cap;
}

/** Merge capabilities into the config object, preserving other config keys. */
function mergeCapabilities(
  existing: Record<string, unknown> | null | undefined,
  capabilities: Record<string, unknown> | undefined,
): Record<string, unknown> | undefined {
  if (capabilities === undefined && existing === undefined) return undefined;
  if (capabilities === undefined) return existing ?? undefined;

  const base = existing ?? {};
  const existingCap =
    typeof base['capabilities'] === 'object' && base['capabilities'] !== null
      ? (base['capabilities'] as Record<string, unknown>)
      : {};

  return {
    ...base,
    capabilities: { ...existingCap, ...capabilities },
  };
}

@Controller('api/agents')
@UseGuards(AuthGuard)
export class AgentConfigsController {
  constructor(@Inject(BRAIN) private readonly brain: Brain) {}

  @Get()
  async list(@CurrentUser() user: { id: string; role?: string }) {
    return this.brain.agents.findAccessible(user.id);
  }

  @Get(':id')
  async findOne(@Param('id') id: string, @CurrentUser() user: { id: string }) {
    const agent = await this.brain.agents.findById(id);
    if (!agent) throw new NotFoundException('Agent not found');
    if (!agent.isSystem && agent.ownerId !== user.id) {
      throw new ForbiddenException('Agent does not belong to the current user');
    }
    return agent;
  }

  @Post()
  async create(@Body() dto: CreateAgentConfigDto, @CurrentUser() user: { id: string }) {
    // Merge capability shorthand fields into config.capabilities (M4-011)
    const capabilities = buildCapabilities(dto);
    const config = mergeCapabilities(dto.config, capabilities);

    return this.brain.agents.create({
      name: dto.name,
      provider: dto.provider,
      model: dto.model,
      status: dto.status,
      projectId: dto.projectId,
      systemPrompt: dto.systemPrompt,
      allowedTools: dto.allowedTools,
      skills: dto.skills,
      isSystem: false,
      config,
      ownerId: user.id,
    });
  }

  @Patch(':id')
  async update(
    @Param('id') id: string,
    @Body() dto: UpdateAgentConfigDto,
    @CurrentUser() user: { id: string; role?: string },
  ) {
    const agent = await this.brain.agents.findById(id);
    if (!agent) throw new NotFoundException('Agent not found');
    if (agent.isSystem && user.role !== 'admin') {
      throw new ForbiddenException('Only admins can update system agents');
    }
    if (!agent.isSystem && agent.ownerId !== user.id) {
      throw new ForbiddenException('Agent does not belong to the current user');
    }

    // Merge capability shorthand fields into config.capabilities (M4-011)
    const capabilities = buildCapabilities(dto);
    const baseConfig =
      dto.config !== undefined
        ? dto.config
        : (agent.config as Record<string, unknown> | null | undefined);
    const config = mergeCapabilities(baseConfig ?? undefined, capabilities);

    // Pass ownerId for user agents so the repo WHERE clause enforces ownership.
    // For system agents (admin path) pass undefined so the WHERE matches only on id.
    const ownerId = agent.isSystem ? undefined : user.id;
    const updated = await this.brain.agents.update(
      id,
      {
        name: dto.name,
        provider: dto.provider,
        model: dto.model,
        status: dto.status,
        projectId: dto.projectId,
        systemPrompt: dto.systemPrompt,
        allowedTools: dto.allowedTools,
        skills: dto.skills,
        config: capabilities !== undefined || dto.config !== undefined ? config : undefined,
      },
      ownerId,
    );
    if (!updated) throw new NotFoundException('Agent not found');
    return updated;
  }

  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  async remove(@Param('id') id: string, @CurrentUser() user: { id: string; role?: string }) {
    const agent = await this.brain.agents.findById(id);
    if (!agent) throw new NotFoundException('Agent not found');
    if (agent.isSystem) {
      throw new ForbiddenException('Cannot delete system agents');
    }
    if (agent.ownerId !== user.id) {
      throw new ForbiddenException('Agent does not belong to the current user');
    }
    // Pass ownerId so the repo WHERE clause enforces ownership at the DB level.
    const deleted = await this.brain.agents.remove(id, user.id);
    if (!deleted) throw new NotFoundException('Agent not found');
  }
}
