/**
 * ElevenLabs Server Tools Service
 *
 * Configures webhook-based server tools (booking, availability) on ElevenLabs agents
 */

import { logger } from '@/lib/logger';
import type { ElevenLabsServerTool, VoiceAgentConfig } from '@/lib/types/voice-agent.types';

const APP_URL = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:9000';

type ToolRecord = {
  id?: string;
  tool_id?: string;
  name?: string;
};

export class ElevenLabsServerToolsService {
  private baseUrl = 'https://api.elevenlabs.io/v1';

  private getApiKey(): string {
    const apiKey = process.env.ELEVENLABS_API_KEY;
    if (!apiKey) throw new Error('ELEVENLABS_API_KEY environment variable is required');
    return apiKey;
  }

  /**
   * Build server tool definitions for booking operations
   */
  buildBookingTools(userToken: string): ElevenLabsServerTool[] {
    const baseBookingUrl = `${APP_URL}/api/voice-agent/server-tools/booking?token=${userToken}`;
    const availabilityUrl = `${APP_URL}/api/voice-agent/server-tools/availability?token=${userToken}`;

    return [
      {
        type: 'webhook',
        name: 'check_availability',
        description: 'Check available appointment time slots for a given date. Call this when the caller asks about available times.',
        webhook: {
          url: availabilityUrl,
          method: 'POST',
        },
        parameters: {
          type: 'object',
          properties: {
            date: {
              type: 'string',
              description: 'The date to check availability for (YYYY-MM-DD format)',
            },
            service: {
              type: 'string',
              description: 'Optional service type to filter availability',
            },
          },
          required: ['date'],
        },
      },
      {
        type: 'webhook',
        name: 'create_booking',
        description: 'Create a new appointment booking. Call this after confirming date, time, and customer details with the caller.',
        webhook: {
          url: baseBookingUrl,
          method: 'POST',
        },
        parameters: {
          type: 'object',
          properties: {
            action: {
              type: 'string',
              description: 'The booking action',
              enum: ['create'],
            },
            customer_name: {
              type: 'string',
              description: 'Full name of the customer',
            },
            customer_phone: {
              type: 'string',
              description: 'Phone number of the customer',
            },
            service: {
              type: 'string',
              description: 'The service being booked',
            },
            date: {
              type: 'string',
              description: 'Appointment date (YYYY-MM-DD)',
            },
            time: {
              type: 'string',
              description: 'Appointment time (HH:MM)',
            },
            notes: {
              type: 'string',
              description: 'Any additional notes from the caller',
            },
          },
          required: ['action', 'customer_name', 'customer_phone', 'service', 'date', 'time'],
        },
      },
      {
        type: 'webhook',
        name: 'cancel_booking',
        description: 'Cancel an existing appointment. Call this when the caller wants to cancel their appointment.',
        webhook: {
          url: baseBookingUrl,
          method: 'POST',
        },
        parameters: {
          type: 'object',
          properties: {
            action: {
              type: 'string',
              description: 'The booking action',
              enum: ['cancel'],
            },
            customer_phone: {
              type: 'string',
              description: 'Phone number to find the booking',
            },
            date: {
              type: 'string',
              description: 'Date of the booking to cancel (YYYY-MM-DD)',
            },
            booking_id: {
              type: 'string',
              description: 'Optional booking ID if known',
            },
          },
          required: ['action', 'customer_phone'],
        },
      },
      {
        type: 'webhook',
        name: 'reschedule_booking',
        description: 'Reschedule an existing appointment to a new date/time.',
        webhook: {
          url: baseBookingUrl,
          method: 'POST',
        },
        parameters: {
          type: 'object',
          properties: {
            action: {
              type: 'string',
              description: 'The booking action',
              enum: ['reschedule'],
            },
            customer_phone: {
              type: 'string',
              description: 'Phone number to find the booking',
            },
            original_date: {
              type: 'string',
              description: 'Original booking date (YYYY-MM-DD)',
            },
            new_date: {
              type: 'string',
              description: 'New appointment date (YYYY-MM-DD)',
            },
            new_time: {
              type: 'string',
              description: 'New appointment time (HH:MM)',
            },
            booking_id: {
              type: 'string',
              description: 'Optional booking ID if known',
            },
          },
          required: ['action', 'customer_phone', 'new_date', 'new_time'],
        },
      },
    ];
  }

  private async listTools(): Promise<ToolRecord[]> {
    const response = await fetch(`${this.baseUrl}/convai/tools`, {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
        'xi-api-key': this.getApiKey(),
      },
    });

    if (!response.ok) {
      return [];
    }

    const payload = await response.json().catch(() => ({}));
    if (Array.isArray(payload)) return payload as ToolRecord[];
    if (Array.isArray((payload as { tools?: ToolRecord[] }).tools)) {
      return (payload as { tools: ToolRecord[] }).tools;
    }
    return [];
  }

  private async createTool(tool: ElevenLabsServerTool): Promise<{ toolId?: string; error?: string }> {
    const payload = {
      tool_config: {
        type: 'webhook',
        name: tool.name,
        description: tool.description,
        response_timeout_secs: 15,
        api_schema: {
          url: tool.webhook.url,
          method: 'POST',
          content_type: 'application/json',
          request_body_schema: tool.parameters,
        },
      },
    };

    const response = await fetch(`${this.baseUrl}/convai/tools`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'xi-api-key': this.getApiKey(),
      },
      body: JSON.stringify(payload),
    });

    if (response.ok) {
      const result = await response.json().catch(() => ({}));
      const toolId = (result?.id || result?.tool_id) as string | undefined;
      if (toolId) {
        return { toolId };
      }
    }

    const details = await response.json().catch(() => ({}));
    // Check if tool already exists
    const existingTools = await this.listTools();
    const existing = existingTools.find((t) => t.name === tool.name);
    if (existing?.id || existing?.tool_id) {
      return { toolId: existing.id || existing.tool_id };
    }
    return {
      error:
        (details?.detail as string | undefined) ||
        (details?.message as string | undefined) ||
        `Failed to create tool ${tool.name}: ${response.status}`,
    };
  }

  private async fetchAgent(agentId: string): Promise<Record<string, unknown> | null> {
    const response = await fetch(`${this.baseUrl}/convai/agents/${agentId}`, {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
        'xi-api-key': this.getApiKey(),
      },
    });

    if (!response.ok) {
      return null;
    }

    return (await response.json().catch(() => null)) as Record<string, unknown> | null;
  }

  private buildToolLinkBody(agent: Record<string, unknown> | null, toolIds: string[]): Record<string, unknown> {
    const conversationConfig =
      (agent?.conversation_config as { agent?: Record<string, unknown> } | undefined) ||
      {};
    const agentConfig = conversationConfig.agent || {};
    const currentPrompt =
      (agentConfig.prompt as Record<string, unknown> | undefined) || {};

    const promptConfig: Record<string, unknown> = {
      ...(typeof currentPrompt.prompt === 'string'
        ? { prompt: currentPrompt.prompt }
        : {
            prompt:
              'You are a helpful appointment assistant. Help callers with booking, rescheduling, and cancellations.',
          }),
      ...(typeof currentPrompt.llm === 'string' ? { llm: currentPrompt.llm } : {}),
      ...(typeof currentPrompt.temperature === 'number'
        ? { temperature: currentPrompt.temperature }
        : {}),
      ...(typeof currentPrompt.max_tokens === 'number'
        ? { max_tokens: currentPrompt.max_tokens }
        : {}),
      tool_ids: toolIds,
    };

    return {
      conversation_config: {
        agent: {
          ...(typeof agentConfig.first_message === 'string'
            ? { first_message: agentConfig.first_message }
            : {}),
          ...(typeof agentConfig.language === 'string'
            ? { language: agentConfig.language }
            : {}),
          prompt: promptConfig,
        },
      },
      use_tool_ids: true,
    };
  }

  /**
   * Build booking tools filtered by enabledTools config
   */
  buildFilteredBookingTools(
    userToken: string,
    enabledTools?: VoiceAgentConfig['enabledTools']
  ): ElevenLabsServerTool[] {
    const allTools = this.buildBookingTools(userToken);
    if (!enabledTools) return allTools;

    const toolFilter: Record<string, boolean> = {
      check_availability: enabledTools.checkAvailability,
      create_booking: enabledTools.createBooking,
      cancel_booking: enabledTools.cancelBooking,
      reschedule_booking: enabledTools.rescheduleBooking,
    };

    return allTools.filter((tool) => toolFilter[tool.name] !== false);
  }

  /**
   * Register server tools on an ElevenLabs agent
   */
  async registerTools(agentId: string, tools: ElevenLabsServerTool[]): Promise<{ success: boolean; error?: string }> {
    try {
      logger.info('Registering server tools on ElevenLabs agent', {
        agentId,
        toolCount: tools.length,
        toolNames: tools.map(t => t.name),
      });

      const toolIds: string[] = [];
      for (const tool of tools) {
        const result = await this.createTool(tool);
        if (!result.toolId) {
          return {
            success: false,
            error: result.error || `Failed to create tool ${tool.name}`,
          };
        }
        toolIds.push(result.toolId);
      }

      const existingAgent = await this.fetchAgent(agentId);
      const response = await fetch(`${this.baseUrl}/convai/agents/${agentId}`, {
        method: 'PATCH',
        headers: {
          'Content-Type': 'application/json',
          'xi-api-key': this.getApiKey(),
        },
        body: JSON.stringify(this.buildToolLinkBody(existingAgent, toolIds)),
      });

      if (!response.ok) {
        const errorData = await response.json().catch(() => ({}));
        logger.warn('Tool IDs registration failed, trying legacy tools patch format', {
          agentId,
          status: response.status,
          errorData,
        });
        const legacyResponse = await fetch(`${this.baseUrl}/convai/agents/${agentId}`, {
          method: 'PATCH',
          headers: {
            'Content-Type': 'application/json',
            'xi-api-key': this.getApiKey(),
          },
          body: JSON.stringify({ tools }),
        });
        if (!legacyResponse.ok) {
          const legacyErrorData = await legacyResponse.json().catch(() => ({}));
          logger.error('Failed to register server tools', new Error(legacyResponse.statusText), {
            status: legacyResponse.status,
            errorData: legacyErrorData,
          });
          return { success: false, error: `Failed: ${legacyResponse.statusText}` };
        }
      }

      logger.info('Successfully registered server tools', { agentId });
      return { success: true };
    } catch (error) {
      logger.error('Error registering server tools', error);
      return { success: false, error: 'Failed to register server tools' };
    }
  }

  /**
   * Remove all server tools from an agent
   */
  async removeTools(agentId: string): Promise<{ success: boolean; error?: string }> {
    try {
      const existingAgent = await this.fetchAgent(agentId);
      const response = await fetch(`${this.baseUrl}/convai/agents/${agentId}`, {
        method: 'PATCH',
        headers: {
          'Content-Type': 'application/json',
          'xi-api-key': this.getApiKey(),
        },
        body: JSON.stringify(this.buildToolLinkBody(existingAgent, [])),
      });

      if (!response.ok) {
        const legacyResponse = await fetch(`${this.baseUrl}/convai/agents/${agentId}`, {
          method: 'PATCH',
          headers: {
            'Content-Type': 'application/json',
            'xi-api-key': this.getApiKey(),
          },
          body: JSON.stringify({ tools: [] }),
        });
        if (!legacyResponse.ok) {
          return { success: false, error: `Failed: ${legacyResponse.statusText}` };
        }
      }

      logger.info('Removed server tools from agent', { agentId });
      return { success: true };
    } catch (error) {
      logger.error('Error removing server tools', error);
      return { success: false, error: 'Failed to remove server tools' };
    }
  }
}

export const elevenLabsServerToolsService = new ElevenLabsServerToolsService();
