package client

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"math"
	"net"
	"net/http"
	"os"
	"strings"
	"time"

	"llm_processor/models"
)

const (
	openRouterURL     = "https://openrouter.ai/api/v1/chat/completions"
	maxRetries        = 5
	initialDelay      = 1 * time.Second
	evaluationTimeout = 15 * time.Minute
	requestTimeout    = 5 * time.Minute
	connectionTimeout = 2 * time.Minute
)

type OpenRouterClient struct {
	apiKey       string
	httpClient   *http.Client
	logger       *log.Logger
	createClient func() *http.Client
}

func NewOpenRouterClient(apiKey string) *OpenRouterClient {
	logFile, err := os.OpenFile("debug.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		log.Fatalf("Failed to open debug.log: %v", err)
	}

	logger := log.New(io.MultiWriter(os.Stdout, logFile), "", log.LstdFlags|log.Lshortfile)
	logger.Println("Initializing OpenRouter client")

	createClient := func() *http.Client {
		transport := &http.Transport{
			MaxIdleConns:        100,
			MaxIdleConnsPerHost: 100,
			IdleConnTimeout:     30 * time.Second,
			DialContext: (&net.Dialer{
				Timeout:   connectionTimeout,
				KeepAlive: 30 * time.Second,
			}).DialContext,
			TLSHandshakeTimeout: 10 * time.Second,
		}

		return &http.Client{
			Timeout:   requestTimeout,
			Transport: transport,
		}
	}

	client := &OpenRouterClient{
		apiKey:       apiKey,
		httpClient:   createClient(),
		logger:       logger,
		createClient: createClient,
	}

	return client
}

type ChatCompletionRequest struct {
	Model    string        `json:"model"`
	Messages []ChatMessage `json:"messages"`
}

type ChatMessage struct {
	Role    string `json:"role"`
	Content string `json:"content"`
}

type ChatCompletionResponse struct {
	Choices []struct {
		Message ChatMessage `json:"message"`
	} `json:"choices"`
}

func (c *OpenRouterClient) EvaluatePaper(ctx context.Context, paper models.Paper, criteria string, model string) (string, error) {
	// Create a new context with evaluation timeout
	evalCtx, cancel := context.WithTimeout(ctx, evaluationTimeout)
	defer cancel()

	startTime := time.Now()
	c.logger.Printf("Starting evaluation for paper: %s\n", paper.Title)
	c.logger.Printf("Evaluation timeout: %s\n", evaluationTimeout)
	fmt.Printf("Starting evaluation for paper: %s at %s\n", paper.Title, startTime.Format(time.RFC3339))

	prompt := fmt.Sprintf(`Evaluate this paper based on the following criteria:
%s

Paper Title: %s
Abstract: %s

Respond ONLY with a JSON object in this exact format:
{
    "decision": "ACCEPT or REJECT",
    "explanation": "Your explanation here"
}

Do not include any other information in your response.

IMPORTANT:
1. The decision MUST be either "ACCEPT" or "REJECT" (uppercase)
2. The explanation should be a clear, concise reason for your decision
3. Do not include any text outside the JSON object
4. Ensure the response is valid JSON (proper quotes and escaping)
5. Do not include any markdown or formatting`, criteria, paper.Title, paper.Abstract)

	reqBody := ChatCompletionRequest{
		Model: model,
		Messages: []ChatMessage{
			{
				Role:    "system",
				Content: "You are a research paper evaluator. Respond only with the requested JSON format.",
			},
			{
				Role:    "user",
				Content: prompt,
			},
		},
	}

	var lastErr error
	for attempt := 0; attempt < maxRetries; attempt++ {
		attemptStart := time.Now()
		c.logger.Printf("Attempt %d started at %s\n", attempt+1, attemptStart.Format(time.RFC3339))
		if attempt > 0 {
			delay := time.Duration(math.Pow(2, float64(attempt))) * initialDelay
			select {
			case <-time.After(delay):
			case <-ctx.Done():
				return "", ctx.Err()
			}
		}

		jsonBody, err := json.Marshal(reqBody)
		if err != nil {
			return "", fmt.Errorf("failed to marshal request body: %w", err)
		}

		req, err := http.NewRequestWithContext(evalCtx, "POST", openRouterURL, bytes.NewBuffer(jsonBody))
		if err != nil {
			return "", fmt.Errorf("failed to create request: %w", err)
		}

		req.Header.Set("Authorization", "Bearer "+c.apiKey)
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("HTTP-Referer", "https://github.com/stwhite/arvix")
		req.Header.Set("X-Title", "ArXiv Paper Processor")

		resp, err := c.httpClient.Do(req)
		if err != nil {
			// Log the specific error type
			c.logger.Printf("Attempt %d error: %v\n", attempt+1, err)

			// Handle context cancellation/timeout
			if errors.Is(err, context.DeadlineExceeded) {
				c.logger.Printf("Context deadline exceeded, retrying...\n")
				lastErr = fmt.Errorf("context deadline exceeded")
				continue
			}

			// On timeout errors, create a new client
			if strings.Contains(err.Error(), "timeout") {
				c.logger.Printf("Timeout detected, recreating HTTP client...\n")
				c.httpClient = c.createClient()
			}

			lastErr = fmt.Errorf("attempt %d: %w", attempt+1, err)
			continue
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			body, _ := io.ReadAll(resp.Body)
			lastErr = fmt.Errorf("attempt %d: openrouter request failed: %s - %s", attempt+1, resp.Status, string(body))
			continue
		}

		var completionResp ChatCompletionResponse
		if err := json.NewDecoder(resp.Body).Decode(&completionResp); err != nil {
			lastErr = fmt.Errorf("attempt %d: failed to decode response: %w", attempt+1, err)
			continue
		}

		if len(completionResp.Choices) == 0 {
			lastErr = fmt.Errorf("attempt %d: no choices in response", attempt+1)
			continue
		}

		rawContent := completionResp.Choices[0].Message.Content

		// Try to parse as JSON first
		var jsonResponse map[string]interface{}
		err = json.Unmarshal([]byte(rawContent), &jsonResponse)
		if err != nil {
			// If direct JSON parsing fails, try extracting from markdown code block
			startIdx := bytes.Index([]byte(rawContent), []byte("```json"))
			if startIdx >= 0 {
				startIdx += len("```json")
				endIdx := bytes.Index([]byte(rawContent[startIdx:]), []byte("```"))
				if endIdx >= 0 {
					jsonContent := rawContent[startIdx : startIdx+endIdx]
					err = json.Unmarshal([]byte(jsonContent), &jsonResponse)
					if err != nil {
						// If still failing, try to parse as raw JSON without code block
						err = json.Unmarshal([]byte(rawContent), &jsonResponse)
					}
				}
			}
		}

		if err == nil {
			// Validate and normalize decision
			if decision, ok := jsonResponse["decision"].(string); ok {
				// Normalize decision value
				normalizedDecision := strings.ToUpper(strings.TrimSpace(decision))
				if strings.Contains(normalizedDecision, "ACCEPT") {
					normalizedDecision = "ACCEPT"
				} else if strings.Contains(normalizedDecision, "REJECT") {
					normalizedDecision = "REJECT"
				}

				if normalizedDecision == "ACCEPT" || normalizedDecision == "REJECT" {
					// Preserve original decision in explanation
					if explanation, ok := jsonResponse["explanation"]; !ok {
						jsonResponse["explanation"] = fmt.Sprintf("Original decision: %s\n", decision)
					} else {
						jsonResponse["explanation"] = fmt.Sprintf("Original decision: %s\n%s", decision, explanation)
					}

					// Parse nested JSON in explanation if present
					if explanation, ok := jsonResponse["explanation"].(string); ok {
						var nested map[string]interface{}
						if err := json.Unmarshal([]byte(explanation), &nested); err == nil {
							jsonResponse["explanation"] = nested
						}
					}

					// Ensure consistent response format
					response := map[string]interface{}{
						"paper": map[string]interface{}{
							"title":    paper.Title,
							"abstract": paper.Abstract,
							"arxiv_id": paper.ArxivID,
						},
						"decision":    normalizedDecision,
						"explanation": jsonResponse["explanation"],
					}

					responseJSON, err := json.Marshal(response)
					if err != nil {
						return "", fmt.Errorf("failed to marshal response: %w", err)
					}

					duration := time.Since(startTime)
					c.logger.Printf("Successfully evaluated paper: %s\n", paper.Title)
					c.logger.Printf("Total time: %s\n", duration)
					c.logger.Printf("Attempts: %d\n", attempt+1)
					return string(responseJSON), nil
				}
			}
		}

		// If direct JSON parsing fails, try extracting from markdown code block
		startIdx := bytes.Index([]byte(rawContent), []byte("```json"))
		if startIdx >= 0 {
			startIdx += len("```json")
			endIdx := bytes.Index([]byte(rawContent[startIdx:]), []byte("```"))
			if endIdx >= 0 {
				jsonContent := rawContent[startIdx : startIdx+endIdx]
				err = json.Unmarshal([]byte(jsonContent), &jsonResponse)
				if err == nil {
					if decision, ok := jsonResponse["decision"].(string); ok {
						if decision == "ACCEPT" || decision == "REJECT" {
							duration := time.Since(startTime)
							c.logger.Printf("Successfully evaluated paper: %s\n", paper.Title)
							c.logger.Printf("Total time: %s\n", duration)
							c.logger.Printf("Attempts: %d\n", attempt+1)
							return jsonContent, nil
						}
					}
				}
			}
		}

		// Fallback parsing if JSON is still invalid
		decision := "ERROR"
		if strings.Contains(rawContent, "ACCEPT") {
			decision = "ACCEPT"
		} else if strings.Contains(rawContent, "REJECT") {
			decision = "REJECT"
		}

		// Create fallback response
		fallbackResponse := map[string]interface{}{
			"decision":    decision,
			"explanation": fmt.Sprintf("Original response: %s", rawContent),
		}
		fallbackJSON, _ := json.Marshal(fallbackResponse)

		duration := time.Since(startTime)
		c.logger.Printf("Fallback parsing used for paper: %s\n", paper.Title)
		c.logger.Printf("Total time: %s\n", duration)
		c.logger.Printf("Attempts: %d\n", attempt+1)
		return string(fallbackJSON), nil
	}

	duration := time.Since(startTime)
	c.logger.Printf("Failed to evaluate paper: %s\n", paper.Title)
	c.logger.Printf("Total time: %s\n", duration)
	c.logger.Printf("Attempts: %d\n", maxRetries)
	return "", fmt.Errorf("max retries (%d) exceeded: %w", maxRetries, lastErr)
}
