package handler

import (
	"encoding/base64"
	"errors"
	"net/http"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"

	"github.com/warmbly/warmbly/internal/api/middleware"
	"github.com/warmbly/warmbly/internal/app/warmupcontent"
	"github.com/warmbly/warmbly/internal/errx"
	"github.com/warmbly/warmbly/internal/models"
	"github.com/warmbly/warmbly/internal/repository"
)

const warmupContentEntity models.AuditEntityType = "warmup_content"

// --- cursor helpers (opaque base64 offset) ---

func decodeOffsetCursor(s string) (int, bool) {
	if s == "" {
		return 0, true
	}
	b, err := base64.StdEncoding.DecodeString(s)
	if err != nil {
		return 0, false
	}
	n, err := strconv.Atoi(string(b))
	if err != nil || n < 0 {
		return 0, false
	}
	return n, true
}

func encodeOffsetCursor(n int) string {
	return base64.StdEncoding.EncodeToString([]byte(strconv.Itoa(n)))
}

type pageMeta struct {
	Total      int     `json:"total"`
	HasMore    bool    `json:"has_more"`
	NextCursor *string `json:"next_cursor"`
}

func pageMetaFor(offset, limit, returned, total int) pageMeta {
	hasMore := offset+returned < total
	var next *string
	if hasMore {
		c := encodeOffsetCursor(offset + limit)
		next = &c
	}
	return pageMeta{Total: total, HasMore: hasMore, NextCursor: next}
}

// AdminWarmupContentOverview returns content-bank counts + generator status.
func (h *Handler) AdminWarmupContentOverview(c *gin.Context) {
	ctx := c.Request.Context()
	stats, err := h.WarmupContentRepo.ConversationStats(ctx)
	if err != nil {
		errx.JSON(c, errx.InternalError())
		return
	}
	totalActive, totalArchived := 0, 0
	for _, s := range stats {
		totalActive += s.Active
		totalArchived += s.Archived
	}
	lastGen, _ := h.WarmupContentRepo.LastGeneratedAt(ctx)
	settings, _ := h.WarmupContentRepo.GetGenerationSettings(ctx)
	if settings == nil {
		def := models.DefaultWarmupGenerationSettings()
		settings = &def
	}

	c.JSON(http.StatusOK, gin.H{
		"total_active":      totalActive,
		"total_archived":    totalArchived,
		"by_pool":           stats,
		"last_generated_at": lastGen,
		"ai_enabled":        settings.Enabled,
		"schedule_enabled":  settings.ScheduleEnabled,
	})
}

type conversationListItem struct {
	ID           uuid.UUID `json:"id"`
	PoolType     string    `json:"pool_type"`
	Segment      string    `json:"segment"`
	Source       string    `json:"source"`
	Theme        string    `json:"theme"`
	Subject      string    `json:"subject"`
	Description  string    `json:"description"`
	MessageCount int       `json:"message_count"`
	Status       string    `json:"status"`
	LintPassed   bool      `json:"lint_passed"`
	UsageCount   int64     `json:"usage_count"`
	CreatedAt    time.Time `json:"created_at"`
}

// AdminListWarmupConversations lists cached conversations with filters.
func (h *Handler) AdminListWarmupConversations(c *gin.Context) {
	offset, ok := decodeOffsetCursor(c.Query("cursor"))
	if !ok {
		errx.JSON(c, errx.New(errx.BadRequest, "invalid cursor"))
		return
	}
	limit := parseLimit(c.Query("limit"), 50)

	f := repository.ConversationFilter{
		PoolType: c.Query("pool"),
		Segment:  c.Query("segment"),
		Source:   c.Query("source"),
		Status:   c.Query("status"),
		Limit:    limit,
		Offset:   offset,
	}
	rows, total, err := h.WarmupContentRepo.ListConversations(c.Request.Context(), f)
	if err != nil {
		errx.JSON(c, errx.InternalError())
		return
	}

	items := make([]conversationListItem, 0, len(rows))
	for _, r := range rows {
		items = append(items, conversationListItem{
			ID: r.ID, PoolType: r.PoolType, Segment: r.Segment, Source: r.Source,
			Theme: r.Theme, Subject: r.Subject, Description: r.Description,
			MessageCount: len(r.Messages), Status: r.Status, LintPassed: r.LintPassed,
			UsageCount: r.UsageCount, CreatedAt: r.CreatedAt,
		})
	}

	c.JSON(http.StatusOK, gin.H{"data": items, "pagination": pageMetaFor(offset, limit, len(rows), total)})
}

// AdminGetWarmupConversation returns a single conversation in full.
func (h *Handler) AdminGetWarmupConversation(c *gin.Context) {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		errx.JSON(c, errx.New(errx.BadRequest, "invalid id"))
		return
	}
	conv, err := h.WarmupContentRepo.GetConversation(c.Request.Context(), id)
	if err != nil {
		errx.JSON(c, errx.InternalError())
		return
	}
	if conv == nil {
		errx.JSON(c, errx.New(errx.NotFound, "conversation not found"))
		return
	}
	c.JSON(http.StatusOK, gin.H{"data": conv})
}

func (h *Handler) setConversationStatus(c *gin.Context, status string) {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		errx.JSON(c, errx.New(errx.BadRequest, "invalid id"))
		return
	}
	if err := h.WarmupContentRepo.SetConversationStatus(c.Request.Context(), id, status); err != nil {
		errx.JSON(c, errx.InternalError())
		return
	}
	h.audit(c, models.AuditActionUpdate, warmupContentEntity, &id, map[string]string{"status": status})
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

// AdminArchiveWarmupConversation archives a conversation (excludes it from selection).
func (h *Handler) AdminArchiveWarmupConversation(c *gin.Context) {
	h.setConversationStatus(c, "archived")
}

// AdminUnarchiveWarmupConversation re-activates a conversation.
func (h *Handler) AdminUnarchiveWarmupConversation(c *gin.Context) {
	h.setConversationStatus(c, "active")
}

// AdminDeleteWarmupConversation permanently removes a conversation.
func (h *Handler) AdminDeleteWarmupConversation(c *gin.Context) {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		errx.JSON(c, errx.New(errx.BadRequest, "invalid id"))
		return
	}
	if err := h.WarmupContentRepo.DeleteConversation(c.Request.Context(), id); err != nil {
		errx.JSON(c, errx.InternalError())
		return
	}
	h.audit(c, models.AuditActionDelete, warmupContentEntity, &id, nil)
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

type generateWarmupRequest struct {
	Count    int    `json:"count"`
	PoolType string `json:"pool_type"`
	Segment  string `json:"segment"`
	Theme    string `json:"theme"`
	Model    string `json:"model"`
}

// AdminGenerateWarmupContent kicks off an offline generation run.
func (h *Handler) AdminGenerateWarmupContent(c *gin.Context) {
	if h.WarmupContentService == nil {
		errx.JSON(c, errx.New(errx.BadRequest, "warmup generation is not configured"))
		return
	}
	var req generateWarmupRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
		return
	}

	adminID := middleware.GetAdminUserID(c)
	jobID, err := h.WarmupContentService.Generate(c.Request.Context(), warmupcontent.GenerateRequest{
		RequestedBy: adminID,
		Trigger:     "manual",
		PoolType:    req.PoolType,
		Segment:     req.Segment,
		Theme:       req.Theme,
		Model:       req.Model,
		Count:       req.Count,
	})
	if err != nil {
		if errors.Is(err, warmupcontent.ErrNotConfigured) {
			errx.JSON(c, errx.New(errx.BadRequest, "warmup AI generation is not configured (set OPENAI_API_KEY)"))
			return
		}
		errx.JSON(c, errx.InternalError())
		return
	}

	h.audit(c, models.AuditActionCreate, warmupContentEntity, &jobID, map[string]string{
		"pool_type": req.PoolType,
		"segment":   req.Segment,
		"count":     strconv.Itoa(req.Count),
	})
	c.JSON(http.StatusOK, gin.H{"job_id": jobID})
}

// AdminListWarmupGenerationJobs lists generation runs (visibility).
func (h *Handler) AdminListWarmupGenerationJobs(c *gin.Context) {
	offset, ok := decodeOffsetCursor(c.Query("cursor"))
	if !ok {
		errx.JSON(c, errx.New(errx.BadRequest, "invalid cursor"))
		return
	}
	limit := parseLimit(c.Query("limit"), 50)

	jobs, total, err := h.WarmupContentRepo.ListGenerationJobs(c.Request.Context(), limit, offset)
	if err != nil {
		errx.JSON(c, errx.InternalError())
		return
	}
	c.JSON(http.StatusOK, gin.H{"data": jobs, "pagination": pageMetaFor(offset, limit, len(jobs), total)})
}

// AdminGetWarmupGenerationJob returns one generation run.
func (h *Handler) AdminGetWarmupGenerationJob(c *gin.Context) {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		errx.JSON(c, errx.New(errx.BadRequest, "invalid id"))
		return
	}
	job, err := h.WarmupContentRepo.GetGenerationJob(c.Request.Context(), id)
	if err != nil {
		errx.JSON(c, errx.InternalError())
		return
	}
	if job == nil {
		errx.JSON(c, errx.New(errx.NotFound, "job not found"))
		return
	}
	c.JSON(http.StatusOK, gin.H{"data": job})
}

// AdminGetWarmupGenerationSettings returns the current generation settings.
func (h *Handler) AdminGetWarmupGenerationSettings(c *gin.Context) {
	settings, err := h.WarmupContentRepo.GetGenerationSettings(c.Request.Context())
	if err != nil {
		errx.JSON(c, errx.InternalError())
		return
	}
	c.JSON(http.StatusOK, gin.H{"data": settings})
}

// AdminUpdateWarmupGenerationSettings replaces the generation settings.
func (h *Handler) AdminUpdateWarmupGenerationSettings(c *gin.Context) {
	var settings models.WarmupGenerationSettings
	if err := c.ShouldBindJSON(&settings); err != nil {
		errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
		return
	}
	adminID := middleware.GetAdminUserID(c)
	if err := h.WarmupContentRepo.SetGenerationSettings(c.Request.Context(), &settings, adminID); err != nil {
		errx.JSON(c, errx.InternalError())
		return
	}
	h.audit(c, models.AuditActionUpdate, warmupContentEntity, nil, map[string]string{
		"enabled":          strconv.FormatBool(settings.Enabled),
		"schedule_enabled": strconv.FormatBool(settings.ScheduleEnabled),
	})
	c.JSON(http.StatusOK, gin.H{"ok": true})
}

type abRow struct {
	ContentSource     string  `json:"content_source"`
	Sent              int     `json:"sent"`
	SpamPlacements    int     `json:"spam_placements"`
	SpamPlacementRate float64 `json:"spam_placement_rate"`
}

// AdminWarmupContentAB returns spam-placement rate by content cohort.
func (h *Handler) AdminWarmupContentAB(c *gin.Context) {
	days := 30
	if v := c.Query("days"); v != "" {
		if n, err := strconv.Atoi(v); err == nil && n > 0 && n <= 365 {
			days = n
		}
	}
	since := time.Now().AddDate(0, 0, -days)
	stats, err := h.WarmupContentRepo.SpamPlacementByCohort(c.Request.Context(), since)
	if err != nil {
		errx.JSON(c, errx.InternalError())
		return
	}
	rows := make([]abRow, 0, len(stats))
	for _, s := range stats {
		rate := 0.0
		if s.Sent > 0 {
			rate = float64(s.SpamPlacements) / float64(s.Sent) * 100
		}
		rows = append(rows, abRow{
			ContentSource: s.ContentSource, Sent: s.Sent,
			SpamPlacements: s.SpamPlacements, SpamPlacementRate: rate,
		})
	}
	c.JSON(http.StatusOK, gin.H{"data": rows, "window_days": days})
}
