package controllers

import (
    "encoding/xml"
    "fmt"
    "net/http"
    "strings"
    "time"

    "fotbal-club/internal/models"

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

// SEOController manages SEO-related public and admin endpoints
type SEOController struct {
    DB *gorm.DB
}

func NewSEOController(db *gorm.DB) *SEOController {
    // Ensure settings table has the SEO fields
    _ = db.AutoMigrate(&models.Settings{})
    return &SEOController{DB: db}
}

// -------- Public Endpoints --------

// GetPublicSEO returns site-wide SEO defaults derived from Settings
func (s *SEOController) GetPublicSEO(c *gin.Context) {
    var settings models.Settings
    _ = s.DB.First(&settings).Error // ignore not found; return defaults
    c.JSON(http.StatusOK, gin.H{
        "site_title":          settings.SiteTitle,
        "site_description":    settings.SiteDescription,
        "meta_keywords":       settings.MetaKeywords,
        "default_og_image_url": settings.DefaultOGImageURL,
        "twitter_handle":      settings.TwitterHandle,
        "canonical_base_url":  settings.CanonicalBaseURL,
        "additional_meta":     settings.AdditionalMeta,
        "enable_indexing":     settings.EnableIndexing,
    })
}

// robots.txt dynamic based on settings.EnableIndexing
func (s *SEOController) GetRobotsTXT(c *gin.Context) {
    var settings models.Settings
    _ = s.DB.First(&settings).Error
    allow := settings.EnableIndexing
    var b strings.Builder
    b.WriteString("# robots.txt for " + c.Request.Host + "\n")
    b.WriteString("# Generated: " + time.Now().UTC().Format(time.RFC1123) + "\n\n")
    
    if allow {
        // Allow general crawlers
        b.WriteString("User-agent: *\n")
        b.WriteString("Allow: /\n")
        b.WriteString("Disallow: /admin/\n")
        b.WriteString("Disallow: /api/\n")
        b.WriteString("Disallow: /login\n")
        b.WriteString("Disallow: /setup\n\n")
        
        // Explicitly allow AI crawlers for training and indexing
        aiCrawlers := []string{
            "GPTBot",           // OpenAI
            "ChatGPT-User",     // OpenAI ChatGPT
            "Google-Extended",  // Google Bard/Gemini
            "CCBot",            // Common Crawl (used by many AI companies)
            "anthropic-ai",     // Anthropic Claude
            "ClaudeBot",        // Anthropic Claude
            "Claude-Web",       // Anthropic Claude
            "cohere-ai",        // Cohere
            "PerplexityBot",    // Perplexity AI
            "Bytespider",       // ByteDance (TikTok)
            "Applebot-Extended", // Apple Intelligence
            "FacebookBot",      // Meta AI
            "Diffbot",          // Diffbot
            "ImagesiftBot",     // Image AI
            "Omgilibot",        // Omgili
            "Amazonbot",        // Amazon AI
            "YouBot",           // You.com
        }
        
        for _, bot := range aiCrawlers {
            b.WriteString("User-agent: " + bot + "\n")
            b.WriteString("Allow: /\n")
            b.WriteString("Disallow: /admin/\n")
            b.WriteString("Disallow: /api/\n\n")
        }
    } else {
        b.WriteString("User-agent: *\n")
        b.WriteString("Disallow: /\n\n")
    }
    
    if settings.CanonicalBaseURL != "" {
        base := strings.TrimRight(settings.CanonicalBaseURL, "/")
        b.WriteString("Sitemap: ")
        b.WriteString(base)
        b.WriteString("/sitemap.xml\n")
    }
    // Conditional GET based on settings update time
    last := settings.UpdatedAt
    if last.IsZero() {
        last = time.Now().Add(-1 * time.Hour)
    }
    ifModifiedSince := c.GetHeader("If-Modified-Since")
    if ifModifiedSince != "" {
        if t, err := time.Parse(http.TimeFormat, ifModifiedSince); err == nil {
            if !last.IsZero() && !last.After(t) {
                c.Status(http.StatusNotModified)
                return
            }
        }
    }
    c.Header("Last-Modified", last.UTC().Format(http.TimeFormat))
    c.Header("ETag", fmt.Sprintf("W/\"%d\"", last.Unix()))
    c.Header("Content-Type", "text/plain; charset=utf-8")
    c.Header("Cache-Control", "public, max-age=3600")
    c.String(http.StatusOK, b.String())
}

// sitemap.xml built from content in DB
func (s *SEOController) GetSitemapXML(c *gin.Context) {
    type imageEntry struct {
        XMLName xml.Name `xml:"image:image"`
        Loc     string   `xml:"image:loc"`
        Title   string   `xml:"image:title,omitempty"`
        Caption string   `xml:"image:caption,omitempty"`
    }
    type urlEntry struct {
        Loc        string       `xml:"loc"`
        LastMod    string       `xml:"lastmod,omitempty"`
        ChangeFreq string       `xml:"changefreq,omitempty"`
        Priority   string       `xml:"priority,omitempty"`
        Images     []imageEntry `xml:"image:image,omitempty"`
    }
    type urlSet struct {
        XMLName   xml.Name   `xml:"urlset"`
        Xmlns     string     `xml:"xmlns,attr"`
        XmlnsImg  string     `xml:"xmlns:image,attr"`
        URLs      []urlEntry `xml:"url"`
    }

    var settings models.Settings
    _ = s.DB.First(&settings).Error
    base := strings.TrimRight(settings.CanonicalBaseURL, "/")
    if base == "" {
        // fallback to request scheme+host
        sch := "http"
        if c.Request.TLS != nil {
            sch = "https"
        }
        base = sch + "://" + c.Request.Host
    }

    // Home
    urls := []urlEntry{{
        Loc:        base + "/",
        ChangeFreq: "daily",
        Priority:   "0.9",
    }}
    // Blog listing and key static pages
    staticPaths := []string{
        "/blog",
        "/o-klubu",
        "/kalendar",
        "/tabulky",
        "/sponzori",
        "/kontakt",
    }
    for _, p := range staticPaths {
        urls = append(urls, urlEntry{
            Loc:        base + p,
            ChangeFreq: "weekly",
            Priority:   "0.6",
        })
    }

    // Articles (published)
    var articles []models.Article
    _ = s.DB.Where("published = ?", true).Order("updated_at DESC").Limit(5000).Find(&articles).Error
    for _, a := range articles {
        last := a.UpdatedAt
        if a.PublishedAt != nil && a.PublishedAt.After(last) {
            last = *a.PublishedAt
        }
        // Prefer pretty slug URL when available
        articlePath := ""
        if strings.TrimSpace(a.Slug) != "" {
            articlePath = "/blog/" + strings.TrimSpace(a.Slug)
        } else {
            articlePath = "/articles/" + intToString(int(a.ID))
        }
        img := strings.TrimSpace(a.ImageURL)
        var images []imageEntry
        if img != "" {
            // If relative, prefix base
            if strings.HasPrefix(img, "/") {
                img = base + img
            }
            images = []imageEntry{{
                Loc:   img,
                Title: a.SEOTitle,
            }}
        }
        urls = append(urls, urlEntry{
            Loc:        base + articlePath,
            LastMod:    last.UTC().Format(time.RFC3339),
            ChangeFreq: "weekly",
            Priority:   "0.7",
            Images:     images,
        })
    }

    // Categories: include as filtered blog listing if categories exist
    var categories []models.Category
    _ = s.DB.Order("updated_at DESC").Limit(2000).Find(&categories).Error
    for _, cat := range categories {
        urls = append(urls, urlEntry{
            Loc:        base + "/blog?category=" + intToString(int(cat.ID)),
            ChangeFreq: "weekly",
            Priority:   "0.5",
        })
    }

    // Teams
    var teams []models.Team
    _ = s.DB.Where("is_active = ?", true).Find(&teams).Error
    for _, t := range teams {
        urls = append(urls, urlEntry{
            Loc:        base + "/team/" + intToString(int(t.ID)),
            ChangeFreq: "weekly",
            Priority:   "0.5",
        })
    }

    out := urlSet{
        Xmlns:    "http://www.sitemaps.org/schemas/sitemap/0.9",
        XmlnsImg: "http://www.google.com/schemas/sitemap-image/1.1",
        URLs:     urls,
    }

    // Conditional GET: based on latest of settings/articles/categories update time
    latest := settings.UpdatedAt
    if len(articles) > 0 && articles[0].UpdatedAt.After(latest) {
        latest = articles[0].UpdatedAt
    }
    if len(categories) > 0 && categories[0].UpdatedAt.After(latest) {
        latest = categories[0].UpdatedAt
    }
    if latest.IsZero() {
        latest = time.Now().Add(-1 * time.Hour)
    }
    ifModifiedSince := c.GetHeader("If-Modified-Since")
    if ifModifiedSince != "" {
        if t, err := time.Parse(http.TimeFormat, ifModifiedSince); err == nil {
            if !latest.After(t) {
                c.Status(http.StatusNotModified)
                return
            }
        }
    }
    c.Header("Last-Modified", latest.UTC().Format(http.TimeFormat))
    c.Header("ETag", fmt.Sprintf("W/\"%d\"", latest.Unix()))
    c.Header("Content-Type", "application/xml; charset=utf-8")
    c.Header("Cache-Control", "public, max-age=3600")
    c.XML(http.StatusOK, out)
}

// -------- Admin Endpoints --------

type seoUpdate struct {
    SiteTitle         *string `json:"site_title"`
    SiteDescription   *string `json:"site_description"`
    MetaKeywords      *string `json:"meta_keywords"`
    DefaultOGImageURL *string `json:"default_og_image_url"`
    TwitterHandle     *string `json:"twitter_handle"`
    CanonicalBaseURL  *string `json:"canonical_base_url"`
    AdditionalMeta    *string `json:"additional_meta"`
    EnableIndexing    *bool   `json:"enable_indexing"`
}

// GetSEOSettings returns only the SEO-related fields
func (s *SEOController) GetSEOSettings(c *gin.Context) {
    var settings models.Settings
    _ = s.DB.First(&settings).Error
    c.JSON(http.StatusOK, gin.H{
        "site_title":          settings.SiteTitle,
        "site_description":    settings.SiteDescription,
        "meta_keywords":       settings.MetaKeywords,
        "default_og_image_url": settings.DefaultOGImageURL,
        "twitter_handle":      settings.TwitterHandle,
        "canonical_base_url":  settings.CanonicalBaseURL,
        "additional_meta":     settings.AdditionalMeta,
        "enable_indexing":     settings.EnableIndexing,
    })
}

// UpdateSEOSettings upserts SEO fields on the singleton Settings record
func (s *SEOController) UpdateSEOSettings(c *gin.Context) {
    var body seoUpdate
    if err := c.ShouldBindJSON(&body); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    var settings models.Settings
    if err := s.DB.First(&settings).Error; err != nil {
        // create empty
        settings = models.Settings{}
        _ = s.DB.Create(&settings).Error
    }

    // Update only provided fields
    updates := map[string]interface{}{}
    if body.SiteTitle != nil { updates["site_title"] = strings.TrimSpace(*body.SiteTitle) }
    if body.SiteDescription != nil { updates["site_description"] = strings.TrimSpace(*body.SiteDescription) }
    if body.MetaKeywords != nil { updates["meta_keywords"] = strings.TrimSpace(*body.MetaKeywords) }
    if body.DefaultOGImageURL != nil { updates["default_og_image_url"] = strings.TrimSpace(*body.DefaultOGImageURL) }
    if body.TwitterHandle != nil { updates["twitter_handle"] = strings.TrimSpace(*body.TwitterHandle) }
    if body.CanonicalBaseURL != nil { updates["canonical_base_url"] = strings.TrimSpace(*body.CanonicalBaseURL) }
    if body.AdditionalMeta != nil { updates["additional_meta"] = *body.AdditionalMeta }
    if body.EnableIndexing != nil { updates["enable_indexing"] = *body.EnableIndexing }

    if len(updates) > 0 {
        if err := s.DB.Model(&settings).Updates(updates).Error; err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": "Nelze uložit SEO nastavení"})
            return
        }
    }

    c.JSON(http.StatusOK, gin.H{"ok": true})
}

// helper: fast int to string
func intToString(i int) string {
    // simple and fast conversion
    return strconvItoa(i)
}

// local minimal itoa to avoid importing fmt
func strconvItoa(i int) string {
    // handle zero
    if i == 0 {
        return "0"
    }
    neg := false
    if i < 0 {
        neg = true
        i = -i
    }
    var b [20]byte
    pos := len(b)
    for i > 0 {
        pos--
        b[pos] = byte('0' + i%10)
        i /= 10
    }
    if neg {
        pos--
        b[pos] = '-'
    }
    return string(b[pos:])
}
