import { loggerService } from '@logger'
import { AgentModelValidationError, agentService, sessionService } from '@main/services/agents'
import { channelManager } from '@main/services/agents/services/channels'
import { schedulerService } from '@main/services/agents/services/SchedulerService'
import type { CherryClawConfiguration, ListAgentsResponse } from '@types'
import { type ReplaceAgentRequest, type UpdateAgentRequest } from '@types'
import type { Request, Response } from 'express'

import type { ValidationRequest } from '../validators/zodValidator'

const logger = loggerService.withContext('ApiServerAgentsHandlers')

const getCherryClawConfig = (agent: { configuration?: unknown }): CherryClawConfiguration =>
  (agent.configuration ?? {}) as CherryClawConfiguration

function syncSchedulerIfNeeded(agentId: string, agent: { configuration?: unknown }): void {
  const config = getCherryClawConfig(agent)
  if (!config.heartbeat_enabled && !config.scheduler_enabled) return

  void schedulerService.syncScheduler()
  schedulerService.ensureHeartbeatTask(agentId, config.heartbeat_interval ?? 30).catch((err) => {
    logger.warn('Failed to sync heartbeat task', {
      agentId,
      error: err instanceof Error ? err.message : String(err)
    })
  })
}

const modelValidationErrorBody = (error: AgentModelValidationError) => ({
  error: {
    message: `Invalid ${error.context.field}: ${error.detail.message}`,
    type: 'invalid_request_error',
    code: error.detail.code
  }
})

/**
 * @swagger
 * /v1/agents:
 *   post:
 *     summary: Create a new agent
 *     description: Creates a new autonomous agent with the specified configuration and automatically
 *       provisions an initial session that mirrors the agent's settings.
 *     tags: [Agents]
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             $ref: '#/components/schemas/CreateAgentRequest'
 *     responses:
 *       201:
 *         description: Agent created successfully
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/AgentEntity'
 *       400:
 *         description: Validation error
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Error'
 *       500:
 *         description: Internal server error
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Error'
 */
export const createAgent = async (req: Request, res: Response): Promise<Response> => {
  try {
    logger.debug('Creating agent')
    logger.debug('Agent payload', { body: req.body })

    const agent = await agentService.createAgent(req.body)

    try {
      logger.info('Agent created', { agentId: agent.id })
      logger.debug('Creating default session for agent', { agentId: agent.id })

      await sessionService.createSession(agent.id, {})
      logger.info('Default session created for agent', { agentId: agent.id })

      // Create heartbeat task if heartbeat is enabled
      const createConfig = getCherryClawConfig(agent)
      if (createConfig.heartbeat_enabled) {
        await schedulerService.ensureHeartbeatTask(agent.id, createConfig.heartbeat_interval ?? 30)
      }

      return res.status(201).json(agent)
    } catch (sessionError: any) {
      logger.error('Failed to create default session for new agent, rolling back agent creation', {
        agentId: agent.id,
        error: sessionError
      })

      try {
        await agentService.deleteAgent(agent.id)
      } catch (rollbackError: any) {
        logger.error('Failed to roll back agent after session creation failure', {
          agentId: agent.id,
          error: rollbackError
        })
      }

      return res.status(500).json({
        error: {
          message: `Failed to create default session for agent: ${sessionError.message}`,
          type: 'internal_error',
          code: 'agent_session_creation_failed'
        }
      })
    }
  } catch (error: any) {
    if (error instanceof AgentModelValidationError) {
      logger.warn('Agent model validation error during create', {
        agentType: error.context.agentType,
        field: error.context.field,
        model: error.context.model,
        detail: error.detail
      })
      return res.status(400).json(modelValidationErrorBody(error))
    }

    logger.error('Error creating agent', { error })
    return res.status(500).json({
      error: {
        message: `Failed to create agent: ${error.message}`,
        type: 'internal_error',
        code: 'agent_creation_failed'
      }
    })
  }
}

/**
 * @swagger
 * /v1/agents:
 *   get:
 *     summary: List all agents
 *     description: Retrieves a paginated list of all agents
 *     tags: [Agents]
 *     parameters:
 *       - in: query
 *         name: limit
 *         schema:
 *           type: integer
 *           minimum: 1
 *           maximum: 100
 *           default: 20
 *         description: Number of agents to return
 *       - in: query
 *         name: offset
 *         schema:
 *           type: integer
 *           minimum: 0
 *           default: 0
 *         description: Number of agents to skip
 *       - in: query
 *         name: sortBy
 *         schema:
 *           type: string
 *           enum: [created_at, updated_at, name, sort_order]
 *           default: sort_order
 *         description: Field to sort by
 *       - in: query
 *         name: orderBy
 *         schema:
 *           type: string
 *           enum: [asc, desc]
 *           default: desc
 *         description: Sort order (asc = ascending, desc = descending). Defaults to asc when sortBy is sort_order.
 *     responses:
 *       200:
 *         description: List of agents
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 data:
 *                   type: array
 *                   items:
 *                     $ref: '#/components/schemas/AgentEntity'
 *                 total:
 *                   type: integer
 *                   description: Total number of agents
 *                 limit:
 *                   type: integer
 *                   description: Number of agents returned
 *                 offset:
 *                   type: integer
 *                   description: Number of agents skipped
 *       400:
 *         description: Validation error
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Error'
 *       500:
 *         description: Internal server error
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Error'
 */
export const listAgents = async (req: Request, res: Response): Promise<Response> => {
  try {
    // Agents are consumed as a flat list across the UI (sidebar, dropdowns, length checks),
    // so the listing endpoint defaults to the schema max instead of the generic 20-page default.
    const limit = req.query.limit ? parseInt(req.query.limit as string) : 1000
    const offset = req.query.offset ? parseInt(req.query.offset as string) : 0
    const sortBy = (req.query.sortBy as 'created_at' | 'updated_at' | 'name' | 'sort_order') || 'sort_order'
    const orderBy = (req.query.orderBy as 'asc' | 'desc') || (sortBy === 'sort_order' ? 'asc' : 'desc')

    logger.debug('Listing agents', { limit, offset, sortBy, orderBy })

    const result = await agentService.listAgents({ limit, offset, sortBy, orderBy })

    logger.info('Agents listed', {
      returned: result.agents.length,
      total: result.total,
      limit,
      offset
    })
    return res.json({
      data: result.agents,
      total: result.total,
      limit,
      offset
    } satisfies ListAgentsResponse)
  } catch (error: any) {
    logger.error('Error listing agents', { error })
    return res.status(500).json({
      error: {
        message: 'Failed to list agents',
        type: 'internal_error',
        code: 'agent_list_failed'
      }
    })
  }
}

/**
 * @swagger
 * /v1/agents/{agentId}:
 *   get:
 *     summary: Get agent by ID
 *     description: Retrieves a specific agent by its ID
 *     tags: [Agents]
 *     parameters:
 *       - in: path
 *         name: agentId
 *         required: true
 *         schema:
 *           type: string
 *         description: Agent ID
 *     responses:
 *       200:
 *         description: Agent details
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/AgentEntity'
 *       404:
 *         description: Agent not found
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Error'
 *       500:
 *         description: Internal server error
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Error'
 */
export const getAgent = async (req: Request, res: Response): Promise<Response> => {
  try {
    const { agentId } = req.params
    logger.debug('Getting agent', { agentId })

    const agent = await agentService.getAgent(agentId)

    if (!agent) {
      logger.warn('Agent not found', { agentId })
      return res.status(404).json({
        error: {
          message: 'Agent not found',
          type: 'not_found',
          code: 'agent_not_found'
        }
      })
    }

    logger.info('Agent retrieved', { agentId })
    return res.json(agent)
  } catch (error: any) {
    logger.error('Error getting agent', { error, agentId: req.params.agentId })
    return res.status(500).json({
      error: {
        message: 'Failed to get agent',
        type: 'internal_error',
        code: 'agent_get_failed'
      }
    })
  }
}

/**
 * @swagger
 * /v1/agents/{agentId}:
 *   put:
 *     summary: Update agent
 *     description: Updates an existing agent with the provided data
 *     tags: [Agents]
 *     parameters:
 *       - in: path
 *         name: agentId
 *         required: true
 *         schema:
 *           type: string
 *         description: Agent ID
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             $ref: '#/components/schemas/CreateAgentRequest'
 *     responses:
 *       200:
 *         description: Agent updated successfully
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/AgentEntity'
 *       400:
 *         description: Validation error
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Error'
 *       404:
 *         description: Agent not found
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Error'
 *       500:
 *         description: Internal server error
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Error'
 */
export const updateAgent = async (req: Request, res: Response): Promise<Response> => {
  const { agentId } = req.params
  try {
    logger.debug('Updating agent', { agentId })
    logger.debug('Replace payload', { body: req.body })

    const { validatedBody } = req as ValidationRequest
    const replacePayload = (validatedBody ?? {}) as ReplaceAgentRequest

    const agent = await agentService.updateAgent(agentId, replacePayload, { replace: true })

    if (!agent) {
      logger.warn('Agent not found for update', { agentId })
      return res.status(404).json({
        error: {
          message: 'Agent not found',
          type: 'not_found',
          code: 'agent_not_found'
        }
      })
    }

    syncSchedulerIfNeeded(agentId, agent)

    logger.info('Agent updated', { agentId })
    return res.json(agent)
  } catch (error: any) {
    if (error instanceof AgentModelValidationError) {
      logger.warn('Agent model validation error during update', {
        agentId,
        agentType: error.context.agentType,
        field: error.context.field,
        model: error.context.model,
        detail: error.detail
      })
      return res.status(400).json(modelValidationErrorBody(error))
    }

    logger.error('Error updating agent', { error, agentId })
    return res.status(500).json({
      error: {
        message: 'Failed to update agent: ' + error.message,
        type: 'internal_error',
        code: 'agent_update_failed'
      }
    })
  }
}

/**
 * @swagger
 * /v1/agents/{agentId}:
 *   patch:
 *     summary: Partially update agent
 *     description: Partially updates an existing agent with only the provided fields
 *     tags: [Agents]
 *     parameters:
 *       - in: path
 *         name: agentId
 *         required: true
 *         schema:
 *           type: string
 *         description: Agent ID
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               name:
 *                 type: string
 *                 description: Agent name
 *               description:
 *                 type: string
 *                 description: Agent description
 *               avatar:
 *                 type: string
 *                 description: Agent avatar URL
 *               instructions:
 *                 type: string
 *                 description: System prompt/instructions
 *               model:
 *                 type: string
 *                 description: Main model ID
 *               plan_model:
 *                 type: string
 *                 description: Optional planning model ID
 *               small_model:
 *                 type: string
 *                 description: Optional small/fast model ID
 *               tools:
 *                 type: array
 *                 items:
 *                   type: string
 *                 description: Tools
 *               mcps:
 *                 type: array
 *                 items:
 *                   type: string
 *                 description: MCP tool IDs
 *               knowledges:
 *                 type: array
 *                 items:
 *                   type: string
 *                 description: Knowledge base IDs
 *               configuration:
 *                 type: object
 *                 description: Extensible settings
 *               accessible_paths:
 *                 type: array
 *                 items:
 *                   type: string
 *                 description: Accessible directory paths
 *               permission_mode:
 *                 type: string
 *                 enum: [readOnly, acceptEdits, bypassPermissions]
 *                 description: Permission mode
 *               max_steps:
 *                 type: integer
 *                 description: Maximum steps the agent can take
 *             description: Only include the fields you want to update
 *     responses:
 *       200:
 *         description: Agent updated successfully
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/AgentEntity'
 *       400:
 *         description: Validation error
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Error'
 *       404:
 *         description: Agent not found
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Error'
 *       500:
 *         description: Internal server error
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Error'
 */
export const patchAgent = async (req: Request, res: Response): Promise<Response> => {
  const { agentId } = req.params
  try {
    logger.debug('Partially updating agent', { agentId })
    logger.debug('Patch payload', { body: req.body })

    const { validatedBody } = req as ValidationRequest
    const updatePayload = (validatedBody ?? {}) as UpdateAgentRequest

    const agent = await agentService.updateAgent(agentId, updatePayload)

    if (!agent) {
      logger.warn('Agent not found for partial update', { agentId })
      return res.status(404).json({
        error: {
          message: 'Agent not found',
          type: 'not_found',
          code: 'agent_not_found'
        }
      })
    }

    syncSchedulerIfNeeded(agentId, agent)

    logger.info('Agent patched', { agentId })
    return res.json(agent)
  } catch (error: any) {
    if (error instanceof AgentModelValidationError) {
      logger.warn('Agent model validation error during partial update', {
        agentId,
        agentType: error.context.agentType,
        field: error.context.field,
        model: error.context.model,
        detail: error.detail
      })
      return res.status(400).json(modelValidationErrorBody(error))
    }

    logger.error('Error partially updating agent', { error, agentId })
    return res.status(500).json({
      error: {
        message: `Failed to partially update agent: ${error.message}`,
        type: 'internal_error',
        code: 'agent_patch_failed'
      }
    })
  }
}

/**
 * @swagger
 * /v1/agents/{agentId}:
 *   delete:
 *     summary: Delete agent
 *     description: Deletes an agent and all associated sessions and logs
 *     tags: [Agents]
 *     parameters:
 *       - in: path
 *         name: agentId
 *         required: true
 *         schema:
 *           type: string
 *         description: Agent ID
 *     responses:
 *       204:
 *         description: Agent deleted successfully
 *       404:
 *         description: Agent not found
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Error'
 *       500:
 *         description: Internal server error
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Error'
 */
export const deleteAgent = async (req: Request, res: Response): Promise<Response> => {
  try {
    const { agentId } = req.params
    logger.debug('Deleting agent', { agentId })

    const deleted = await agentService.deleteAgent(agentId)

    if (!deleted) {
      logger.warn('Agent not found for deletion', { agentId })
      return res.status(404).json({
        error: {
          message: 'Agent not found',
          type: 'not_found',
          code: 'agent_not_found'
        }
      })
    }

    // Disconnect all channel adapters for the deleted agent
    void channelManager.disconnectAgent(agentId)

    logger.info('Agent deleted', { agentId })
    return res.status(204).send()
  } catch (error: any) {
    logger.error('Error deleting agent', { error, agentId: req.params.agentId })
    return res.status(500).json({
      error: {
        message: 'Failed to delete agent',
        type: 'internal_error',
        code: 'agent_delete_failed'
      }
    })
  }
}

/**
 * @swagger
 * /v1/agents/reorder:
 *   put:
 *     summary: Reorder agents
 *     description: Sets the display order of agents based on the provided array of agent IDs
 *     tags: [Agents]
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               ordered_ids:
 *                 type: array
 *                 items:
 *                   type: string
 *                 description: Array of agent IDs in the desired display order
 *             required:
 *               - ordered_ids
 *     responses:
 *       200:
 *         description: Agents reordered successfully
 *       400:
 *         description: Validation error
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Error'
 *       500:
 *         description: Internal server error
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Error'
 */
export const reorderAgents = async (req: Request, res: Response): Promise<Response> => {
  try {
    const { ordered_ids } = req.body

    if (
      !Array.isArray(ordered_ids) ||
      ordered_ids.length === 0 ||
      !ordered_ids.every((id: unknown) => typeof id === 'string' && id.length > 0)
    ) {
      return res.status(400).json({
        error: {
          message: 'ordered_ids must be a non-empty array of agent IDs',
          type: 'invalid_request_error',
          code: 'invalid_ordered_ids'
        }
      })
    }

    logger.debug('Reordering agents', { count: ordered_ids.length })
    await agentService.reorderAgents(ordered_ids)

    logger.info('Agents reordered', { count: ordered_ids.length })
    return res.json({ success: true })
  } catch (error: any) {
    logger.error('Error reordering agents', { error })
    return res.status(500).json({
      error: {
        message: 'Failed to reorder agents',
        type: 'internal_error',
        code: 'agent_reorder_failed'
      }
    })
  }
}
