/*
 * ChatCLI - Command Line Interface for LLM interaction
 * Copyright (c) 2024 Edilson Freitas
 * License: Apache-2.0
 */
package agent

import (
	"context"
	"fmt"
	"os"
	"time"

	"github.com/diillson/chatcli/llm/client"
	"github.com/diillson/chatcli/models"
	"github.com/diillson/chatcli/utils"
	"go.uber.org/zap"
)

// ContextManager gerencia contexto de execução para o agente
type ContextManager struct {
	logger         *zap.Logger
	defaultTimeout time.Duration
}

// NewContextManager cria uma nova instância do gerenciador de contexto
func NewContextManager(logger *zap.Logger) *ContextManager {
	// Ler timeout de ENV ou usar padrão
	timeout := 10 * time.Minute
	const maxTimeout = 1 * time.Hour
	if envTimeout := os.Getenv("CHATCLI_AGENT_CMD_TIMEOUT"); envTimeout != "" {
		if d, err := time.ParseDuration(envTimeout); err == nil && d > 0 {
			timeout = d
			if timeout > maxTimeout {
				timeout = maxTimeout
			}
		}
	}

	return &ContextManager{
		logger:         logger,
		defaultTimeout: timeout,
	}
}

// CreateExecutionContext cria um contexto com timeout para execução
func (cm *ContextManager) CreateExecutionContext() (context.Context, context.CancelFunc) {
	cm.logger.Debug("Criando contexto de execução",
		zap.Duration("timeout", cm.defaultTimeout))
	return context.WithTimeout(context.Background(), cm.defaultTimeout)
}

// RequestLLMContinuation solicita continuação à LLM com contexto adicional
func (cm *ContextManager) RequestLLMContinuation(
	ctx context.Context,
	llmClient interface{},
	history []models.Message,
	previousCommand string,
	output string,
	stderr string,
	userContext string,
) (string, error) {
	llm, ok := llmClient.(client.LLMClient)
	if !ok {
		return "", fmt.Errorf("llmClient does not implement client.LLMClient interface")
	}

	newCtx, cancel := context.WithTimeout(ctx, 5*time.Minute)
	defer cancel()

	// Sanitizar saídas
	outSafe := utils.SanitizeSensitiveText(output)
	errSafe := utils.SanitizeSensitiveText(stderr)

	// Construir prompt
	prompt := fmt.Sprintf(`O comando sugerido anteriormente foi:
    %s

    O resultado (stdout) foi:
    %s

    O erro (stderr) foi:
    %s

    Por favor, sugira uma correção ou próximos passos baseados no resultado e no contexto fornecido.
    Forneça comandos executáveis no formato apropriado.`, previousCommand, outSafe, errSafe)

	if userContext != "" {
		prompt += fmt.Sprintf("\n\nContexto adicional fornecido pelo usuário:\n%s", userContext)
	}

	cm.logger.Debug("Solicitando continuação à LLM",
		zap.Int("history_size", len(history)),
		zap.Int("prompt_length", len(prompt)))

	response, err := llm.SendPrompt(newCtx, prompt, history, 0)
	if err != nil {
		cm.logger.Error("LLM continuation request failed", zap.Error(err))
		return "", fmt.Errorf("LLM request failed: %w", err)
	}

	return response, nil
}

// RequestLLMWithPreExecutionContext solicita refinamento antes da execução
func (cm *ContextManager) RequestLLMWithPreExecutionContext(
	ctx context.Context,
	llmClient interface{},
	history []models.Message,
	originalCommand string,
	userContext string,
) (string, error) {
	llm, ok := llmClient.(client.LLMClient)
	if !ok {
		return "", fmt.Errorf("llmClient does not implement client.LLMClient interface")
	}

	newCtx, cancel := context.WithTimeout(ctx, 5*time.Minute)
	defer cancel()

	prompt := fmt.Sprintf(`O comando que você sugeriu foi:
`+"```"+`
%s
`+"```"+`

Antes de executá-lo, o usuário forneceu o seguinte contexto ou instrução adicional:
%s

Por favor, revise o comando sugerido com base neste novo contexto. Se necessário, modifique-o ou sugira um novo conjunto de comandos. Apresente os novos comandos no formato executável apropriado.`, originalCommand, userContext)

	cm.logger.Debug("Solicitando refinamento pré-execução",
		zap.String("original_command", originalCommand),
		zap.Int("context_length", len(userContext)))

	response, err := llm.SendPrompt(newCtx, prompt, history, 0)
	if err != nil {
		cm.logger.Error("LLM pre-execution refinement failed", zap.Error(err))
		return "", fmt.Errorf("LLM request failed: %w", err)
	}

	return response, nil
}

// SetDefaultTimeout atualiza o timeout padrão
func (cm *ContextManager) SetDefaultTimeout(timeout time.Duration) {
	if timeout > 0 {
		cm.defaultTimeout = timeout
		cm.logger.Info("Timeout padrão atualizado", zap.Duration("new_timeout", timeout))
	}
}

// GetDefaultTimeout retorna o timeout padrão configurado
func (cm *ContextManager) GetDefaultTimeout() time.Duration {
	return cm.defaultTimeout
}
