package controllers

import (
	"bytes"
	"encoding/json"
	"fmt"
	"html"
	"net/http"
	"regexp"
	"strings"
	"time"
	"os"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
)

// AIController handles AI-assisted endpoints
type AIController struct {
	DB *gorm.DB
}

// GenerateAboutPage creates about page content using the OpenRouter API
func (ac *AIController) GenerateAboutPage(c *gin.Context) {
	var req aiAboutRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	clubName := strings.TrimSpace(req.ClubName)
	if clubName == "" {
		clubName = "Fotbalový klub"
	}
	style := strings.TrimSpace(req.Style)
	if style == "" {
		style = "default"
	}
	audience := strings.TrimSpace(req.Audience)
	if audience == "" {
		audience = "fanoušci klubu"
	}

	system := "Jsi zkušený editor klubových webů. Pomůžeš napsat stránku 'O klubu'. DŮLEŽITÉ: Odpovídej v GRAMATICKY SPRÁVNÉ češtině - používej pouze existující česká slova a správné tvary. Žádné neologismy nebo negramatické tvary. Odpovídej česky, srozumitelně a profesionálně."
	user := fmt.Sprintf("Poznámky k vytvoření stránky O klubu:\n---\n%s\n---\nNázev klubu: %s\nPreferovaný styl: %s\nCílové publikum: %s\n\nPovinné požadavky:\n1) Zachovej fakta z poznámek a rozšiř je o kontext (historie, hodnoty, úspěchy, tým, zázemí, komunita).\n2) Rozděl text do sekcí s HTML nadpisy (h2/h3) a odstavci (p). Můžeš použít seznamy (ul/li) tam, kde to dává smysl. Bez inline stylů.\n3) Napiš krátký podnadpis (subtitle) vystihující náladu klubu.\n4) Vytvoř SEO titulek (do 60 znaků) a SEO popis (do 160 znaků).\n5) Odpověz POUZE JSON: {\"title\":\"...\", \"subtitle\":\"...\", \"html\":\"...\", \"seo_title\":\"...\", \"seo_description\":\"...\"}.\n6) HTML pole musí obsahovat kompletní obsah stránky dle požadavků.\n", strings.TrimSpace(req.Prompt), clubName, style, audience)

	baseURL := getOpenRouterBaseURL()
	apiKey := getOpenRouterAPIKey()
	if strings.TrimSpace(apiKey) == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "OPENROUTER_API_KEY není nastaven"})
		return
	}

	model := getOpenRouterModel()
	if model == "" {
		model = "mistralai/mistral-small-3.2-24b-instruct:free"
	}
	fallbackModel := getOpenRouterFallbackModel()
	if fallbackModel == "" {
		fallbackModel = "mistralai/mistral-nemo:free"
	}

	callModel := func(modelName string) (string, int, error) {
		payload := map[string]interface{}{
			"model": modelName,
			"messages": []map[string]string{
				{"role": "system", "content": system},
				{"role": "user", "content": user},
			},
			"temperature": 0.5,
			"max_tokens": 2200,
		}
		body, _ := json.Marshal(payload)

		endpoint := strings.TrimRight(baseURL, "/") + "/chat/completions"
		reqHTTP, err := http.NewRequest("POST", endpoint, bytes.NewReader(body))
		if err != nil {
			return "", http.StatusInternalServerError, err
		}
		reqHTTP.Header.Set("Authorization", "Bearer "+apiKey)
		reqHTTP.Header.Set("Content-Type", "application/json")
		if ref := strings.TrimSpace(getenv("OPENROUTER_SITE_URL")); ref != "" {
			reqHTTP.Header.Set("HTTP-Referer", ref)
		}
		if ttl := strings.TrimSpace(getenv("OPENROUTER_APP_NAME")); ttl != "" {
			reqHTTP.Header.Set("X-Title", ttl)
		}

		client := &http.Client{Timeout: 45 * time.Second}
		resp, err := client.Do(reqHTTP)
		if err != nil {
			return "", http.StatusBadGateway, err
		}
		defer resp.Body.Close()
		if resp.StatusCode < 200 || resp.StatusCode >= 300 {
			var e map[string]interface{}
			_ = json.NewDecoder(resp.Body).Decode(&e)
			return "", resp.StatusCode, fmt.Errorf("OpenRouter API error: %v", e)
		}

		var or struct {
			Choices []struct {
				Message struct {
					Content string `json:"content"`
				} `json:"message"`
			} `json:"choices"`
		}
		if err := json.NewDecoder(resp.Body).Decode(&or); err != nil {
			return "", http.StatusBadGateway, err
		}
		if len(or.Choices) == 0 {
			return "", http.StatusBadGateway, fmt.Errorf("empty choices")
		}
		return strings.TrimSpace(or.Choices[0].Message.Content), http.StatusOK, nil
	}

	content, _, err := callModel(model)
	if err != nil || strings.TrimSpace(content) == "" {
		if fbContent, _, fbErr := callModel(fallbackModel); fbErr == nil && strings.TrimSpace(fbContent) != "" {
			content = fbContent
		} else {
			if err != nil {
				c.JSON(http.StatusBadGateway, gin.H{"error": "OpenRouter selhal (včetně fallbacku)", "details": err.Error()})
			} else if fbErr != nil {
				c.JSON(http.StatusBadGateway, gin.H{"error": "OpenRouter fallback selhal", "details": fbErr.Error()})
			} else {
				c.JSON(http.StatusBadGateway, gin.H{"error": "OpenRouter vrátil prázdnou odpověď"})
			}
			return
		}
	}

	var out aiAboutResponse
	sanitized := sanitizeAIResponse(content)
	if err := json.Unmarshal([]byte(sanitized), &out); err != nil {
		re := regexp.MustCompile(`(?s)\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}`)
		if m := re.FindString(sanitized); m != "" {
			_ = json.Unmarshal([]byte(m), &out)
		}
	}

	if out.HTML != "" {
		out.HTML = html.UnescapeString(out.HTML)
	}
	if out.SEOTitle == "" {
		out.SEOTitle = fmt.Sprintf("%s | %s", clubName, "Oficiální informace")
	}
	if out.SEODescription == "" {
		out.SEODescription = fmt.Sprintf("Přečtěte si informace o klubu %s.", clubName)
	}
	if out.Title == "" {
		out.Title = clubName
	}
	if out.Subtitle == "" {
		out.Subtitle = "Oficiální klubový profil"
	}
	if out.HTML == "" {
		out.HTML = fmt.Sprintf("<h2>O klubu %s</h2><p>%s</p>", htmlEscape(clubName), htmlEscape(strings.TrimSpace(req.Prompt)))
	}

	c.JSON(http.StatusOK, out)
}

func NewAIController(db *gorm.DB) *AIController {
	return &AIController{DB: db}
}

type aiBlogRequest struct {
	// Short user input that describes the topic/notes for the blog in Czech
	Prompt string `json:"prompt" binding:"required"`
	// Optional extra hints
	Audience string `json:"audience"`
	MinWords int    `json:"min_words"`
}

type aiBlogResponse struct {
	Title string `json:"title"`
	Slug  string `json:"slug"`
	HTML  string `json:"html"`
}

type aiAboutRequest struct {
	Prompt    string `json:"prompt" binding:"required"`
	ClubName  string `json:"club_name"`
	Style     string `json:"style"`
	Audience  string `json:"audience"`
}

type aiAboutResponse struct {
	Title          string `json:"title"`
	Subtitle       string `json:"subtitle"`
	HTML           string `json:"html"`
	SEOTitle       string `json:"seo_title"`
	SEODescription string `json:"seo_description"`
}

// GenerateBlog creates a blog article using the OpenRouter API (with Mistral models)
func (ac *AIController) GenerateBlog(c *gin.Context) {
	var req aiBlogRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if req.MinWords <= 0 {
		req.MinWords = 450
	}

	// Build instruction in Czech - emphasizing user text as primary source, but allow expansion if needed
	system := "Jsi asistent pro tvorbu článků. Tvým HLAVNÍM úkolem je: PŘEVZÍT TEXT OD UŽIVATELE a rozvinout ho do čitelného článku. Vždy vycházej z textu uživatele - zachovej VŠECHNY jeho informace, fakta a události. Pokud je text krátký, přidej kontext, rozvinutí a souvislosti, ale vždy kolem témat a informací z textu uživatele. Rozděl text do logických odstavců, přidej vhodné HTML značky (nadpisy h2/h3, odstavce p, seznamy ul/ol). DŮLEŽITÉ: Píšeš v GRAMATICKY SPRÁVNÉ češtině - používej pouze existující česká slova a správné tvary. Žádné neologismy nebo negramatické tvary (např. místo 'nevděkovaný' použij 'nevděčný'). Píšeš srozumitelně a čtivě pro fotbalové fanoušky. HTML výstup bez inline stylů."
	user := fmt.Sprintf("Text od uživatele (VŽDY z něj vycházej, zachovej všechny jeho informace):\n---\n%s\n---\nPublikum: %s\nCílová délka: %d slov.\n\nPOVINNÉ POŽADAVKY:\n1) ZACHOVEJ všechny informace, jména, události a fakta z textu uživatele. To je ZÁKLAD článku.\n2) Pokud je text krátký (pod %d slov), ROZVIŇ ho - přidej kontext, atmosféru, detaily kolem událostí z textu uživatele. Buď čtivý a zajímavý.\n3) Pokud je text dostatečně dlouhý, pouze ho strukturuj do HTML s nadpisy a odstavci.\n4) Vygeneruj výstižný titulek vycházející z obsahu textu uživatele.\n5) Vytvoř URL slug (3-5 slov, max. 40 znaků, lowercase, bez diakritiky, jen písmena/číslice a pomlčky).\n6) Odpověz POUZE JSON: {\"title\": \"...\", \"slug\": \"...\", \"html\": \"...\"}\n7) HTML obsah = text uživatele + rozvinutí (pokud nutné) strukturovaný do HTML tagů (h2, p, ul, ol). BEZ inline stylů.\n\nPAMATUJ: Text uživatele = základ. Pokud je krátký, rozviň ho čtivě a zajímavě pro %s.\n", strings.TrimSpace(req.Prompt), strings.TrimSpace(req.Audience), req.MinWords, req.MinWords, strings.TrimSpace(req.Audience))

    // Prepare OpenRouter request
    baseURL := getOpenRouterBaseURL()
    apiKey := getOpenRouterAPIKey()
    if strings.TrimSpace(apiKey) == "" {
        c.JSON(http.StatusBadRequest, gin.H{"error": "OPENROUTER_API_KEY není nastaven"})
        return
    }

    // Primary and fallback models
    model := getOpenRouterModel()
    if model == "" {
        model = "mistralai/mistral-small-3.2-24b-instruct:free"
    }
    fallbackModel := getOpenRouterFallbackModel()
    if fallbackModel == "" {
        fallbackModel = "mistralai/mistral-nemo:free"
    }

    // Helper to call OpenRouter with a given model and return content
    callModel := func(modelName string) (string, int, error) {
        payload := map[string]interface{}{
            "model": modelName,
            "messages": []map[string]string{
                {"role": "system", "content": system},
                {"role": "user", "content": user},
            },
            "temperature": 0.5,
            "max_tokens": 2000,
        }
        body, _ := json.Marshal(payload)

        endpoint := strings.TrimRight(baseURL, "/") + "/chat/completions"
        reqHTTP, err := http.NewRequest("POST", endpoint, bytes.NewReader(body))
        if err != nil {
            return "", http.StatusInternalServerError, err
        }
        reqHTTP.Header.Set("Authorization", "Bearer "+apiKey)
        reqHTTP.Header.Set("Content-Type", "application/json")
        // Optional but recommended headers for OpenRouter
        if ref := strings.TrimSpace(getenv("OPENROUTER_SITE_URL")); ref != "" { reqHTTP.Header.Set("HTTP-Referer", ref) }
        if ttl := strings.TrimSpace(getenv("OPENROUTER_APP_NAME")); ttl != "" { reqHTTP.Header.Set("X-Title", ttl) }

        client := &http.Client{Timeout: 45 * time.Second}
        resp, err := client.Do(reqHTTP)
        if err != nil {
            return "", http.StatusBadGateway, err
        }
        defer resp.Body.Close()
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            var e map[string]interface{}
            _ = json.NewDecoder(resp.Body).Decode(&e)
            return "", resp.StatusCode, fmt.Errorf("OpenRouter API error: %v", e)
        }

        // OpenAI-compatible response
        var or struct {
            Choices []struct {
                Message struct {
                    Content string `json:"content"`
                } `json:"message"`
            } `json:"choices"`
        }
        if err := json.NewDecoder(resp.Body).Decode(&or); err != nil {
            return "", http.StatusBadGateway, err
        }
        if len(or.Choices) == 0 {
            return "", http.StatusBadGateway, fmt.Errorf("empty choices")
        }
        return strings.TrimSpace(or.Choices[0].Message.Content), http.StatusOK, nil
    }

    // Try primary, then fallback
    content, _, err := callModel(model)
    if err != nil || strings.TrimSpace(content) == "" {
        // Attempt fallback model
        if fbContent, _, fbErr := callModel(fallbackModel); fbErr == nil && strings.TrimSpace(fbContent) != "" {
            content = fbContent
        } else {
            // Provide the primary error if available
            if err != nil {
                c.JSON(http.StatusBadGateway, gin.H{"error": "OpenRouter selhal (včetně fallbacku)", "details": err.Error()})
            } else if fbErr != nil {
                c.JSON(http.StatusBadGateway, gin.H{"error": "OpenRouter fallback selhal", "details": fbErr.Error()})
            } else {
                c.JSON(http.StatusBadGateway, gin.H{"error": "OpenRouter vrátil prázdnou odpověď"})
            }
            return
        }
    }

    // Sanitize and parse JSON returned by the model
    var out aiBlogResponse
    
    // Clean up the response: remove markdown code blocks, backticks, etc.
    sanitized := sanitizeAIResponse(content)
    
    // Try to parse the sanitized content
    if err := json.Unmarshal([]byte(sanitized), &out); err != nil {
        // Best-effort: try to find JSON block
        re := regexp.MustCompile(`(?s)\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}`)
        if m := re.FindString(sanitized); m != "" {
            _ = json.Unmarshal([]byte(m), &out)
        }
    }
    
    // Decode HTML entities in the html field
    if out.HTML != "" {
        out.HTML = html.UnescapeString(out.HTML)
    }

    // Fallbacks if the model did not provide title/slug
    if out.Title == "" {
        out.Title = deriveTitle(req.Prompt)
    }
    // Validate slug: short, independent from title. If not valid, derive from prompt.
    if !isValidShortSlug(out.Slug) || out.Slug == slugify(out.Title) {
        out.Slug = shortSlugFromPrompt(req.Prompt)
    }
    if out.HTML == "" {
        // Wrap raw content as paragraph fallback
        out.HTML = "<h1>" + htmlEscape(out.Title) + "</h1><p>" + htmlEscape(content) + "</p>"
    }

    c.JSON(http.StatusOK, out)
}

// Helpers for OpenRouter config
func getOpenRouterAPIKey() string {
    if v := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(getenv("OPENROUTER_API_KEY")), "\"")); v != "" {
        return v
    }
    return ""
}

func getOpenRouterBaseURL() string {
    if v := strings.TrimSpace(getenv("OPENROUTER_BASE_URL")); v != "" {
        return v
    }
    return "https://openrouter.ai/api/v1"
}

func getOpenRouterModel() string {
    if v := strings.TrimSpace(getenv("OPENROUTER_MODEL")); v != "" {
        return v
    }
    return ""
}

func getOpenRouterFallbackModel() string {
    if v := strings.TrimSpace(getenv("OPENROUTER_FALLBACK_MODEL")); v != "" {
        return v
    }
    return ""
}

// Small utility wrappers to avoid importing os directly multiple times
func getenv(k string) string { return strings.TrimSpace(strings.ReplaceAll(strings.ReplaceAll(strings.TrimSpace(os.Getenv(k)), "\r", ""), "\n", "")) }

// deriveTitle returns a readable title from user prompt
func deriveTitle(s string) string {
	s = strings.TrimSpace(s)
	if s == "" {
		return "Novinky z klubu"
	}
	// Capitalize first letter, keep it concise
	if len(s) > 120 {
		s = s[:120]
	}
	return strings.ToUpper(string([]rune(s)[:1])) + s[1:]
}

// slugify creates a URL-friendly slug without diacritics
func slugify(s string) string {
	s = strings.ToLower(strings.TrimSpace(s))
	// Replace diacritics (basic map); for full support, consider x/text/unicode/norm and transform
	replacer := strings.NewReplacer(
		"á", "a", "č", "c", "ď", "d", "é", "e", "ě", "e", "í", "i", "ň", "n",
		"ó", "o", "ř", "r", "š", "s", "ť", "t", "ú", "u", "ů", "u", "ý", "y", "ž", "z",
	)
	s = replacer.Replace(s)
	// Replace any non alnum with hyphen
	re := regexp.MustCompile("[^a-z0-9]+")
	s = re.ReplaceAllString(s, "-")
	s = strings.Trim(s, "-")
	if s == "" {
		return "clanek"
	}
	return s
}

// isValidShortSlug checks basic constraints: non-empty, <= 40 chars, 3-5 words (by hyphens), allowed charset
func isValidShortSlug(s string) bool {
	s = strings.TrimSpace(s)
	if s == "" { return false }
	if len(s) > 40 { return false }
	parts := strings.Split(s, "-")
	// filter empty parts
	w := 0
	for _, p := range parts { if p != "" { w++ } }
	if w < 3 || w > 5 { return false }
	// allowed chars: a-z0-9-
	re := regexp.MustCompile(`^[a-z0-9-]+$`)
	return re.MatchString(s)
}

// shortSlugFromPrompt creates a compact, independent slug from the prompt text
func shortSlugFromPrompt(prompt string) string {
	p := strings.ToLower(strings.TrimSpace(prompt))
	if p == "" { return "clanek" }
	// basic diacritics removal via slugify, then split to words
	p = slugify(p)
	parts := strings.Split(p, "-")
	// simple Czech stopwords list (subset)
	stop := map[string]struct{}{"a":{},"i":{},"v":{},"ve":{},"z":{},"za":{},"od":{},"do":{},"u":{},"o":{},"s":{},"se":{},"na":{},"po":{},"pod":{},"nad":{},"proti":{},"pri":{},"bez":{},"k":{},"ke":{},"ten":{},"ta":{},"to":{},"ty":{},"tento":{},"tato":{},"toto":{},"jak":{},"jako":{},"ze":{}}
	var kept []string
	for _, w := range parts {
		if w == "" { continue }
		if _, ok := stop[w]; ok { continue }
		kept = append(kept, w)
		if len(kept) >= 5 { break }
	}
	if len(kept) == 0 { kept = parts }
	// prefer 3-5 words, trim to 4 if too many
	if len(kept) > 5 { kept = kept[:5] }
	if len(kept) >= 4 { kept = kept[:4] }
	s := strings.Join(kept, "-")
	if len(s) > 40 { s = s[:40] }
	s = strings.Trim(s, "-")
	if !isValidShortSlug(s) {
		// final fallback
		s = slugify(deriveTitle(prompt))
		if len(s) > 40 { s = s[:40] }
		s = strings.Trim(s, "-")
	}
	return s
}

func htmlEscape(s string) string {
	s = strings.ReplaceAll(s, "&", "&amp;")
	s = strings.ReplaceAll(s, "<", "&lt;")
	s = strings.ReplaceAll(s, ">", "&gt;")
	s = strings.ReplaceAll(s, "\"", "&quot;")
	s = strings.ReplaceAll(s, "'", "&#39;")
	return s
}

// sanitizeAIResponse cleans up AI response to extract valid JSON
// Handles markdown code blocks, extra backticks, and other formatting issues
func sanitizeAIResponse(content string) string {
	// Trim whitespace
	content = strings.TrimSpace(content)
	
	// Remove markdown code block markers (```json, ``json, `, etc.)
	// Handle various formats: ```json\n{...}\n```, ``json{...}``, `{...}`
	content = regexp.MustCompile(`^\s*`+"`"+`{1,3}\s*json\s*`).ReplaceAllString(content, "")
	content = regexp.MustCompile(`\s*`+"`"+`{1,3}\s*$`).ReplaceAllString(content, "")
	
	// Remove any remaining backticks at start/end
	content = strings.Trim(content, "`")
	content = strings.TrimSpace(content)
	
	return content
}
