/**
 * Role-Based AI Service
 *
 * Servicio de IA con control de acceso basado en roles.
 * Extiende la funcionalidad del AIService con validación de permisos.
 *
 * Basado en: michangarrito MCH-012/MCH-013
 */

import { Repository } from 'typeorm';
import {
  AIModel,
  AIConversation,
  AIMessage,
  AIPrompt,
  AIUsageLog,
  AITenantQuota,
} from '../entities';
import { AIService } from './ai.service';
import {
  ERPRole,
  getERPRole,
  hasToolAccess,
  getToolsForRole,
  getRoleConfig,
} from '../roles/erp-roles.config';
import { generateSystemPrompt, PromptVariables } from '../prompts';

export interface ChatContext {
  tenantId: string;
  userId: string;
  userRole: string; // Rol de BD
  branchId?: string;
  branchName?: string;
  conversationId?: string;
  metadata?: Record<string, any>;
}

export interface ChatMessage {
  role: 'user' | 'assistant' | 'system';
  content: string;
  toolCalls?: ToolCall[];
  toolResults?: ToolResult[];
}

export interface ToolCall {
  id: string;
  name: string;
  arguments: Record<string, any>;
}

export interface ToolResult {
  toolCallId: string;
  result: any;
  error?: string;
}

export interface ChatResponse {
  message: string;
  conversationId: string;
  toolsUsed?: string[];
  tokensUsed: {
    input: number;
    output: number;
    total: number;
  };
  model: string;
}

export interface ToolDefinition {
  name: string;
  description: string;
  inputSchema: Record<string, any>;
  handler?: (args: any, context: ChatContext) => Promise<any>;
}

/**
 * Servicio de IA con Role-Based Access Control
 */
export class RoleBasedAIService extends AIService {
  private conversationHistory: Map<string, ChatMessage[]> = new Map();
  private toolRegistry: Map<string, ToolDefinition> = new Map();

  constructor(
    modelRepository: Repository<AIModel>,
    conversationRepository: Repository<AIConversation>,
    messageRepository: Repository<AIMessage>,
    promptRepository: Repository<AIPrompt>,
    usageLogRepository: Repository<AIUsageLog>,
    quotaRepository: Repository<AITenantQuota>,
    private tenantConfigProvider?: TenantConfigProvider
  ) {
    super(
      modelRepository,
      conversationRepository,
      messageRepository,
      promptRepository,
      usageLogRepository,
      quotaRepository
    );
  }

  /**
   * Registrar un tool disponible
   */
  registerTool(tool: ToolDefinition): void {
    this.toolRegistry.set(tool.name, tool);
  }

  /**
   * Registrar múltiples tools
   */
  registerTools(tools: ToolDefinition[]): void {
    for (const tool of tools) {
      this.registerTool(tool);
    }
  }

  /**
   * Obtener tools permitidos para un rol
   */
  getToolsForRole(role: ERPRole): ToolDefinition[] {
    const allowedToolNames = getToolsForRole(role);
    const tools: ToolDefinition[] = [];

    for (const toolName of allowedToolNames) {
      const tool = this.toolRegistry.get(toolName);
      if (tool) {
        tools.push(tool);
      }
    }

    return tools;
  }

  /**
   * Verificar si el usuario puede usar un tool
   */
  canUseTool(context: ChatContext, toolName: string): boolean {
    const erpRole = getERPRole(context.userRole);
    return hasToolAccess(erpRole, toolName);
  }

  /**
   * Enviar mensaje de chat con role-based access
   */
  async chat(
    context: ChatContext,
    message: string,
    options?: {
      modelCode?: string;
      temperature?: number;
      maxTokens?: number;
    }
  ): Promise<ChatResponse> {
    const erpRole = getERPRole(context.userRole);
    const roleConfig = getRoleConfig(erpRole);

    if (!roleConfig) {
      throw new Error(`Invalid role: ${context.userRole}`);
    }

    // Verificar quota
    const quotaCheck = await this.checkQuotaAvailable(context.tenantId);
    if (!quotaCheck.available) {
      throw new Error(quotaCheck.reason || 'Quota exceeded');
    }

    // Obtener o crear conversación
    let conversation: AIConversation;
    if (context.conversationId) {
      const existing = await this.findConversation(context.conversationId);
      if (existing) {
        conversation = existing;
      } else {
        conversation = await this.createConversation(context.tenantId, context.userId, {
          title: message.substring(0, 100),
          metadata: {
            role: erpRole,
            branchId: context.branchId,
          },
        });
      }
    } else {
      conversation = await this.createConversation(context.tenantId, context.userId, {
        title: message.substring(0, 100),
        metadata: {
          role: erpRole,
          branchId: context.branchId,
        },
      });
    }

    // Obtener historial de conversación
    const history = await this.getConversationHistory(
      conversation.id,
      roleConfig.maxConversationHistory
    );

    // Generar system prompt
    const systemPrompt = await this.generateSystemPromptForContext(context, erpRole);

    // Obtener tools permitidos
    const allowedTools = this.getToolsForRole(erpRole);

    // Obtener modelo
    const model = options?.modelCode
      ? await this.findModelByCode(options.modelCode)
      : await this.getDefaultModel(context.tenantId);

    if (!model) {
      throw new Error('No AI model available');
    }

    // Construir mensajes para la API
    const messages: ChatMessage[] = [
      { role: 'system', content: systemPrompt },
      ...history,
      { role: 'user', content: message },
    ];

    // Guardar mensaje del usuario
    await this.addMessage(conversation.id, {
      role: 'user',
      content: message,
    });

    // Llamar a la API de AI (OpenRouter)
    const response = await this.callAIProvider(model, messages, allowedTools, options);

    // Procesar tool calls si hay
    let finalResponse = response.content;
    const toolsUsed: string[] = [];

    if (response.toolCalls && response.toolCalls.length > 0) {
      for (const toolCall of response.toolCalls) {
        // Validar que el tool esté permitido
        if (!this.canUseTool(context, toolCall.name)) {
          continue; // Ignorar tools no permitidos
        }

        toolsUsed.push(toolCall.name);

        // Ejecutar tool
        const tool = this.toolRegistry.get(toolCall.name);
        if (tool?.handler) {
          try {
            await tool.handler(toolCall.arguments, context);
            // El resultado se incorpora a la respuesta
            // En una implementación completa, se haría otra llamada a la API
          } catch (error: any) {
            console.error(`Tool ${toolCall.name} failed:`, error.message);
          }
        }
      }
    }

    // Guardar respuesta del asistente
    await this.addMessage(conversation.id, {
      role: 'assistant',
      content: finalResponse,
      metadata: {
        model: model.code,
        toolsUsed,
        tokensUsed: response.tokensUsed,
      },
    });

    // Registrar uso
    await this.logUsage(context.tenantId, {
      modelId: model.id,
      conversationId: conversation.id,
      promptTokens: response.tokensUsed.input,
      completionTokens: response.tokensUsed.output,
      totalTokens: response.tokensUsed.total,
      cost: this.calculateCost(model, response.tokensUsed),
      usageType: 'chat',
    });

    // Incrementar quota
    await this.incrementQuotaUsage(
      context.tenantId,
      1,
      response.tokensUsed.total,
      this.calculateCost(model, response.tokensUsed)
    );

    return {
      message: finalResponse,
      conversationId: conversation.id,
      toolsUsed: toolsUsed.length > 0 ? toolsUsed : undefined,
      tokensUsed: response.tokensUsed,
      model: model.code,
    };
  }

  /**
   * Obtener historial de conversación formateado
   */
  private async getConversationHistory(
    conversationId: string,
    maxMessages: number
  ): Promise<ChatMessage[]> {
    const messages = await this.findMessages(conversationId);

    // Tomar los últimos N mensajes
    const recentMessages = messages.slice(-maxMessages);

    return recentMessages.map((msg) => ({
      role: msg.role as 'user' | 'assistant',
      content: msg.content,
    }));
  }

  /**
   * Generar system prompt para el contexto
   */
  private async generateSystemPromptForContext(
    context: ChatContext,
    role: ERPRole
  ): Promise<string> {
    // Obtener configuración del tenant
    const tenantConfig = this.tenantConfigProvider
      ? await this.tenantConfigProvider.getConfig(context.tenantId)
      : null;

    const variables: PromptVariables = {
      businessName: tenantConfig?.businessName || 'ERP System',
      currentDate: new Date().toLocaleDateString('es-MX'),
      currentBranch: context.branchName,
      maxDiscount: tenantConfig?.maxDiscount,
      storeHours: tenantConfig?.storeHours,
    };

    return generateSystemPrompt(role, variables);
  }

  /**
   * Obtener modelo por defecto para el tenant
   */
  private async getDefaultModel(_tenantId: string): Promise<AIModel | null> {
    // Buscar configuración del tenant o usar default
    const models = await this.findAllModels();
    return models.find((m) => m.isDefault) || models[0] || null;
  }

  /**
   * Llamar al proveedor de AI (OpenRouter)
   */
  private async callAIProvider(
    model: AIModel,
    messages: ChatMessage[],
    tools: ToolDefinition[],
    options?: { temperature?: number; maxTokens?: number }
  ): Promise<{
    content: string;
    toolCalls?: ToolCall[];
    tokensUsed: { input: number; output: number; total: number };
  }> {
    // Aquí iría la integración con OpenRouter
    // Por ahora retornamos un placeholder
    const apiKey = process.env.OPENROUTER_API_KEY;
    if (!apiKey) {
      throw new Error('OPENROUTER_API_KEY not configured');
    }

    const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json',
        'HTTP-Referer': process.env.APP_URL || 'https://erp.local',
      },
      body: JSON.stringify({
        model: model.modelId || model.code,
        messages: messages.map((m) => ({
          role: m.role,
          content: m.content,
        })),
        tools: tools.length > 0
          ? tools.map((t) => ({
              type: 'function',
              function: {
                name: t.name,
                description: t.description,
                parameters: t.inputSchema,
              },
            }))
          : undefined,
        temperature: options?.temperature ?? 0.7,
        max_tokens: options?.maxTokens ?? 2000,
      }),
    });

    if (!response.ok) {
      const errorData = await response.json().catch(() => ({}) as Record<string, any>);
      throw new Error((errorData as any).error?.message || 'AI provider error');
    }

    const data = await response.json() as Record<string, any>;
    const choice = (data.choices as any[])?.[0];

    return {
      content: choice?.message?.content || '',
      toolCalls: choice?.message?.tool_calls?.map((tc: any) => ({
        id: tc.id,
        name: tc.function?.name,
        arguments: JSON.parse(tc.function?.arguments || '{}'),
      })),
      tokensUsed: {
        input: (data.usage as any)?.prompt_tokens || 0,
        output: (data.usage as any)?.completion_tokens || 0,
        total: (data.usage as any)?.total_tokens || 0,
      },
    };
  }

  /**
   * Calcular costo de uso
   */
  private calculateCost(
    model: AIModel,
    tokens: { input: number; output: number }
  ): number {
    const inputCost = (tokens.input / 1000) * (model.inputCostPer1k || 0);
    const outputCost = (tokens.output / 1000) * (model.outputCostPer1k || 0);
    return inputCost + outputCost;
  }

  /**
   * Limpiar conversación antigua (para liberar memoria)
   */
  cleanupOldConversations(_maxAgeMinutes: number = 60): void {
    // En una implementación real, esto estaría en Redis o similar
    // Por ahora limpiamos el Map en memoria
    for (const [_key, _value] of this.conversationHistory) {
      // TODO: Implementar lógica de limpieza basada en timestamp
    }
  }
}

/**
 * Interface para proveedor de configuración de tenant
 */
export interface TenantConfigProvider {
  getConfig(tenantId: string): Promise<{
    businessName: string;
    maxDiscount?: number;
    storeHours?: string;
    defaultModel?: string;
  } | null>;
}
