package ai

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"

	"customer-support-system/internal/models"
	"customer-support-system/pkg/config"
	"customer-support-system/pkg/logger"
)

// OpenAIRequest represents a request to the OpenAI API
type OpenAIRequest struct {
	Model       string          `json:"model"`
	Messages    []OpenAIMessage `json:"messages"`
	MaxTokens   int             `json:"max_tokens,omitempty"`
	Temperature float64         `json:"temperature,omitempty"`
	TopP        float64         `json:"top_p,omitempty"`
	Stream      bool            `json:"stream,omitempty"`
}

// OpenAIMessage represents a message in the OpenAI API
type OpenAIMessage struct {
	Role    string `json:"role"`
	Content string `json:"content"`
}

// OpenAIResponse represents a response from the OpenAI API
type OpenAIResponse struct {
	ID      string         `json:"id"`
	Object  string         `json:"object"`
	Created int64          `json:"created"`
	Model   string         `json:"model"`
	Choices []OpenAIChoice `json:"choices"`
	Usage   OpenAIUsage    `json:"usage"`
}

// OpenAIChoice represents a choice in the OpenAI API response
type OpenAIChoice struct {
	Index        int           `json:"index"`
	Message      OpenAIMessage `json:"message"`
	FinishReason string        `json:"finish_reason"`
}

// OpenAIUsage represents usage statistics in the OpenAI API response
type OpenAIUsage struct {
	PromptTokens     int `json:"prompt_tokens"`
	CompletionTokens int `json:"completion_tokens"`
	TotalTokens      int `json:"total_tokens"`
}

// OllamaRequest represents a request to the Ollama API
type OllamaRequest struct {
	Model   string         `json:"model"`
	Prompt  string         `json:"prompt"`
	Stream  bool           `json:"stream,omitempty"`
	Options map[string]any `json:"options,omitempty"`
}

// OllamaResponse represents a response from the Ollama API
type OllamaResponse struct {
	Model     string `json:"model"`
	CreatedAt string `json:"created_at"`
	Response  string `json:"response"`
	Done      bool   `json:"done"`
}

// AIService handles AI operations
type AIService struct {
	openAIConfig config.APIConfig
	localConfig  config.APIConfig
	httpClient   *http.Client
}

// NewAIService creates a new AI service
func NewAIService() *AIService {
	return &AIService{
		openAIConfig: config.AppConfig.AI.OpenAI,
		localConfig:  config.AppConfig.AI.Local,
		httpClient: &http.Client{
			Timeout: 30 * time.Second,
		},
	}
}

// QueryOpenAI sends a query to the OpenAI API
func (s *AIService) QueryOpenAI(ctx context.Context, prompt string, conversationHistory []models.Message) (string, error) {
	startTime := time.Now()

	// Convert conversation history to OpenAI messages
	messages := s.convertToOpenAIMessages(prompt, conversationHistory)

	// Create request
	req := OpenAIRequest{
		Model:       s.openAIConfig.Model,
		Messages:    messages,
		MaxTokens:   s.openAIConfig.MaxTokens,
		Temperature: s.openAIConfig.Temperature,
		TopP:        s.openAIConfig.TopP,
	}

	// Marshal request to JSON
	reqBody, err := json.Marshal(req)
	if err != nil {
		logger.WithError(err).Error("Failed to marshal OpenAI request")
		return "", fmt.Errorf("failed to marshal request: %w", err)
	}

	// Create HTTP request
	httpReq, err := http.NewRequestWithContext(ctx, "POST", "https://api.openai.com/v1/chat/completions", bytes.NewBuffer(reqBody))
	if err != nil {
		logger.WithError(err).Error("Failed to create OpenAI HTTP request")
		return "", fmt.Errorf("failed to create HTTP request: %w", err)
	}

	// Set headers
	httpReq.Header.Set("Content-Type", "application/json")
	httpReq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", s.openAIConfig.APIKey))

	// Send request
	resp, err := s.httpClient.Do(httpReq)
	if err != nil {
		logger.WithError(err).Error("Failed to send OpenAI request")
		return "", fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	// Read response body
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		logger.WithError(err).Error("Failed to read OpenAI response")
		return "", fmt.Errorf("failed to read response: %w", err)
	}

	// Check status code
	if resp.StatusCode != http.StatusOK {
		logger.WithField("status_code", resp.StatusCode).
			WithField("response", string(respBody)).
			Error("OpenAI API returned non-200 status code")
		return "", fmt.Errorf("OpenAI API returned status code %d: %s", resp.StatusCode, string(respBody))
	}

	// Parse response
	var openAIResp OpenAIResponse
	if err := json.Unmarshal(respBody, &openAIResp); err != nil {
		logger.WithError(err).Error("Failed to parse OpenAI response")
		return "", fmt.Errorf("failed to parse response: %w", err)
	}

	// Extract response text
	if len(openAIResp.Choices) == 0 {
		logger.Error("OpenAI API returned no choices")
		return "", fmt.Errorf("OpenAI API returned no choices")
	}

	responseText := openAIResp.Choices[0].Message.Content

	// Log AI interaction
	duration := time.Since(startTime)
	logger.LogAIInteraction(
		s.openAIConfig.Model,
		len(prompt),
		len(responseText),
		duration,
		true,
		nil,
	)

	return responseText, nil
}

// QueryOllama sends a query to the Ollama API
func (s *AIService) QueryOllama(ctx context.Context, prompt string, conversationHistory []models.Message) (string, error) {
	startTime := time.Now()

	// Create request
	req := OllamaRequest{
		Model:  s.localConfig.Model,
		Prompt: s.buildOllamaPrompt(prompt, conversationHistory),
		Stream: false,
		Options: map[string]any{
			"temperature": s.localConfig.Temperature,
			"top_p":       s.localConfig.TopP,
			"num_predict": s.localConfig.MaxTokens,
		},
	}

	// Marshal request to JSON
	reqBody, err := json.Marshal(req)
	if err != nil {
		logger.WithError(err).Error("Failed to marshal Ollama request")
		return "", fmt.Errorf("failed to marshal request: %w", err)
	}

	// Create HTTP request
	httpReq, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/api/generate", s.localConfig.Endpoint), bytes.NewBuffer(reqBody))
	if err != nil {
		logger.WithError(err).Error("Failed to create Ollama HTTP request")
		return "", fmt.Errorf("failed to create HTTP request: %w", err)
	}

	// Set headers
	httpReq.Header.Set("Content-Type", "application/json")

	// Send request
	resp, err := s.httpClient.Do(httpReq)
	if err != nil {
		logger.WithError(err).Error("Failed to send Ollama request")
		return "", fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	// Read response body
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		logger.WithError(err).Error("Failed to read Ollama response")
		return "", fmt.Errorf("failed to read response: %w", err)
	}

	// Check status code
	if resp.StatusCode != http.StatusOK {
		logger.WithField("status_code", resp.StatusCode).
			WithField("response", string(respBody)).
			Error("Ollama API returned non-200 status code")
		return "", fmt.Errorf("Ollama API returned status code %d: %s", resp.StatusCode, string(respBody))
	}

	// Parse response
	var ollamaResp OllamaResponse
	if err := json.Unmarshal(respBody, &ollamaResp); err != nil {
		logger.WithError(err).Error("Failed to parse Ollama response")
		return "", fmt.Errorf("failed to parse response: %w", err)
	}

	// Extract response text
	responseText := ollamaResp.Response

	// Log AI interaction
	duration := time.Since(startTime)
	logger.LogAIInteraction(
		s.localConfig.Model,
		len(prompt),
		len(responseText),
		duration,
		true,
		nil,
	)

	return responseText, nil
}

// Query sends a query to the appropriate AI model based on complexity
func (s *AIService) Query(ctx context.Context, prompt string, conversationHistory []models.Message, complexity int) (string, error) {
	// Determine which AI model to use based on complexity
	if complexity >= 7 { // High complexity, use OpenAI
		return s.QueryOpenAI(ctx, prompt, conversationHistory)
	} else { // Low to medium complexity, use local LLM
		return s.QueryOllama(ctx, prompt, conversationHistory)
	}
}

// AnalyzeComplexity analyzes the complexity of a prompt
func (s *AIService) AnalyzeComplexity(prompt string) int {
	// Simple heuristic for complexity analysis
	// In a real implementation, this would be more sophisticated

	complexity := 0

	// Length factor
	if len(prompt) > 100 {
		complexity += 2
	}
	if len(prompt) > 200 {
		complexity += 2
	}

	// Question type factor
	if strings.Contains(prompt, "?") {
		complexity += 1
	}

	// Technical terms factor
	technicalTerms := []string{"API", "database", "server", "code", "programming", "software", "algorithm"}
	for _, term := range technicalTerms {
		if strings.Contains(strings.ToLower(prompt), strings.ToLower(term)) {
			complexity += 1
		}
	}

	// Multiple questions factor
	questionCount := strings.Count(prompt, "?")
	if questionCount > 1 {
		complexity += questionCount - 1
	}

	// Cap complexity at 10
	if complexity > 10 {
		complexity = 10
	}

	return complexity
}

// convertToOpenAIMessages converts conversation history to OpenAI messages
func (s *AIService) convertToOpenAIMessages(prompt string, conversationHistory []models.Message) []OpenAIMessage {
	messages := []OpenAIMessage{}

	// Add system message
	messages = append(messages, OpenAIMessage{
		Role:    "system",
		Content: "You are a helpful customer support assistant. Provide clear, concise, and accurate answers to customer questions.",
	})

	// Add conversation history
	for _, msg := range conversationHistory {
		role := "user"
		if msg.IsAI {
			role = "assistant"
		}
		messages = append(messages, OpenAIMessage{
			Role:    role,
			Content: msg.Content,
		})
	}

	// Add current prompt
	messages = append(messages, OpenAIMessage{
		Role:    "user",
		Content: prompt,
	})

	return messages
}

// buildOllamaPrompt builds a prompt for Ollama from conversation history
func (s *AIService) buildOllamaPrompt(prompt string, conversationHistory []models.Message) string {
	var builder strings.Builder

	// Add system instruction
	builder.WriteString("You are a helpful customer support assistant. Provide clear, concise, and accurate answers to customer questions.\n\n")

	// Add conversation history
	for _, msg := range conversationHistory {
		if msg.IsAI {
			builder.WriteString("Assistant: ")
		} else {
			builder.WriteString("User: ")
		}
		builder.WriteString(msg.Content)
		builder.WriteString("\n\n")
	}

	// Add current prompt
	builder.WriteString("User: ")
	builder.WriteString(prompt)
	builder.WriteString("\n\nAssistant: ")

	return builder.String()
}

// GetAvailableModels returns the available AI models
func (s *AIService) GetAvailableModels() []models.AIModel {
	return []models.AIModel{
		{
			Name:        "OpenAI GPT-4",
			Type:        "openai",
			Model:       s.openAIConfig.Model,
			MaxTokens:   s.openAIConfig.MaxTokens,
			Temperature: s.openAIConfig.Temperature,
			TopP:        s.openAIConfig.TopP,
			Active:      true,
			Priority:    2,
			Description: "OpenAI's GPT-4 model for complex queries",
		},
		{
			Name:        "Local LLaMA",
			Type:        "local",
			Model:       s.localConfig.Model,
			Endpoint:    s.localConfig.Endpoint,
			MaxTokens:   s.localConfig.MaxTokens,
			Temperature: s.localConfig.Temperature,
			TopP:        s.localConfig.TopP,
			Active:      true,
			Priority:    1,
			Description: "Local LLaMA model for simple queries",
		},
	}
}
