package controllers

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

	"github.com/gofiber/fiber/v2"
	"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
)

type DataIa struct {
	Prompt string `json:"prompt"`
	Model  string `json:"model"`
	Stream bool   `json:"stream"`
}

type respuestaOllama struct {
	Response string `json:"response"`
	Done     bool   `json:"done"`
	Error    string `json:"error"`
}

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

type openaiChatReq struct {
	Model    string          `json:"model"`
	Messages []openaiChatMsg `json:"messages"`
	Stream   bool            `json:"stream"`
}

type openaiChatChoice struct {
	Delta struct {
		Content string `json:"content"`
	} `json:"delta"`
	FinishReason *string `json:"finish_reason"`
}

type openaiChatResp struct {
	Choices []openaiChatChoice `json:"choices"`
}

type geminiPart struct {
	Text string `json:"text"`
}

type geminiContent struct {
	Parts []geminiPart `json:"parts"`
}

type geminiReq struct {
	Contents []geminiContent `json:"contents"`
}

type geminiCandidate struct {
	Content      geminiContent `json:"content"`
	FinishReason *string       `json:"finishReason"`
}

type geminiResp struct {
	Candidates []geminiCandidate `json:"candidates"`
}

func GeneraTextoStream(c *fiber.Ctx) error {
	var data DataIa
	if err := c.BodyParser(&data); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Datos inválidos"})
	}
	data.Stream = true

	config, err := models.GetAiConfigForService("ia")
	if err != nil {
		return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
			"error": "No hay configuración de IA activa. Configura una en /app/ai-config",
		})
	}

	if data.Model == "" {
		data.Model = config.ModelName
	}
	if data.Model == "" {
		data.Model = "gemma3:1b"
	}

	provider := strings.ToLower(config.Provider)
	baseURL := strings.TrimRight(config.BaseURL, "/")
	hasV1 := strings.Contains(baseURL, "/v1")

	var endpoint string
	var bodyReader io.Reader
	useOpenAI := provider != "ollama" || hasV1
	useGemini := provider == "gemini"

	if useGemini {
		model := data.Model
		endpoint = fmt.Sprintf("https://generativelanguage.googleapis.com/v1beta/models/%s:streamGenerateContent?alt=sse&key=%s", model, config.ApiKey)
		gReq := geminiReq{
			Contents: []geminiContent{
				{Parts: []geminiPart{{Text: data.Prompt}}},
			},
		}
		jsonData, _ := json.Marshal(gReq)
		bodyReader = bytes.NewBuffer(jsonData)
	} else if !useOpenAI {
		// Native Ollama API
		endpoint = baseURL + "/api/generate"
		jsonData, _ := json.Marshal(data)
		bodyReader = bytes.NewBuffer(jsonData)
	} else {
		// OpenAI-compatible (/v1/chat/completions)
		baseURL = strings.TrimSuffix(baseURL, "/v1")
		endpoint = baseURL + "/v1/chat/completions"
		chatReq := openaiChatReq{
			Model: data.Model,
			Messages: []openaiChatMsg{
				{Role: "user", Content: data.Prompt},
			},
			Stream: true,
		}
		jsonData, _ := json.Marshal(chatReq)
		bodyReader = bytes.NewBuffer(jsonData)
	}

	log.Printf("[IA] endpoint=%s model=%s provider=%s", endpoint, data.Model, provider)

	req, err := http.NewRequest("POST", endpoint, bodyReader)
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")

	// Auth
	if useGemini {
		// API key ya va en la URL
	} else if provider == "ollama" && config.ApiKey != "" && config.ApiKey != "ollama" {
		req.SetBasicAuth("ollama", config.ApiKey)
	} else if config.ApiKey != "" {
		req.Header.Set("Authorization", "Bearer "+config.ApiKey)
	}

	client := &http.Client{Timeout: 120 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		log.Printf("[IA] Error conectando: %v", err)
		return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{
			"error": fmt.Sprintf("Error conectando a %s: %v", provider, err),
		})
	}
	if resp.StatusCode != http.StatusOK {
		bodyBytes, _ := io.ReadAll(resp.Body)
		resp.Body.Close()
		log.Printf("[IA] %s status %d: %s", provider, resp.StatusCode, string(bodyBytes))
		return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{
			"error":   fmt.Sprintf("%s respondió con status %d", provider, resp.StatusCode),
			"details": string(bodyBytes),
		})
	}

	c.Set("Content-Type", "text/plain; charset=utf-8")
	c.Set("Cache-Control", "no-cache")
	c.Set("X-Accel-Buffering", "no")

	c.Context().SetBodyStreamWriter(func(w *bufio.Writer) {
		defer resp.Body.Close()
		scanner := bufio.NewScanner(resp.Body)
		scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
		for scanner.Scan() {
			line := scanner.Text()
			if line == "" {
				continue
			}
			if useGemini {
				if !strings.HasPrefix(line, "data: ") {
					continue
				}
				var msg geminiResp
				if err := json.Unmarshal([]byte(line[6:]), &msg); err != nil {
					continue
				}
				if len(msg.Candidates) > 0 && len(msg.Candidates[0].Content.Parts) > 0 {
					text := msg.Candidates[0].Content.Parts[0].Text
					if text != "" {
						fmt.Fprint(w, text)
						w.Flush()
					}
					if msg.Candidates[0].FinishReason != nil {
						break
					}
				}
			} else if useOpenAI {
				var msg openaiChatResp
				if err := json.Unmarshal([]byte(line), &msg); err != nil {
					if err != io.EOF {
						log.Printf("[IA] Error decodificando respuesta OpenAI: %v", err)
					}
					continue
				}
				if len(msg.Choices) == 0 {
					continue
				}
				content := msg.Choices[0].Delta.Content
				if content != "" {
					fmt.Fprint(w, content)
					w.Flush()
				}
				if msg.Choices[0].FinishReason != nil {
					break
				}
			} else {
				var msg respuestaOllama
				if err := json.Unmarshal([]byte(line), &msg); err != nil {
					continue
				}
				if msg.Error != "" {
					log.Printf("[IA] Ollama error: %s", msg.Error)
					fmt.Fprintf(w, "[Error: %s]", msg.Error)
					w.Flush()
					break
				}
				if msg.Response != "" {
					fmt.Fprint(w, msg.Response)
					w.Flush()
				}
				if msg.Done {
					break
				}
			}
		}
	})

	return nil
}
