package handlers

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

	"git.mchus.pro/mchus/quoteforge/internal/models"
	"git.mchus.pro/mchus/quoteforge/internal/services"
	"github.com/gin-gonic/gin"
)

type ExportHandler struct {
	exportService  *services.ExportService
	configService  services.ConfigurationGetter
	projectService *services.ProjectService
	dbUsername     string
}

func NewExportHandler(
	exportService *services.ExportService,
	configService services.ConfigurationGetter,
	projectService *services.ProjectService,
	dbUsername string,
) *ExportHandler {
	return &ExportHandler{
		exportService:  exportService,
		configService:  configService,
		projectService: projectService,
		dbUsername:     dbUsername,
	}
}

type ExportRequest struct {
	Name        string `json:"name" binding:"required"`
	ProjectName string `json:"project_name"`
	ProjectUUID string `json:"project_uuid"`
	Article     string `json:"article"`
	ServerCount int    `json:"server_count"`
	PricelistID *uint  `json:"pricelist_id"`
	Items       []struct {
		LotName   string  `json:"lot_name" binding:"required"`
		Quantity  int     `json:"quantity" binding:"required,min=1"`
		UnitPrice float64 `json:"unit_price"`
	} `json:"items" binding:"required,min=1"`
	Notes string `json:"notes"`
}

type ProjectExportOptionsRequest struct {
	IncludeLOT        bool    `json:"include_lot"`
	IncludeBOM        bool    `json:"include_bom"`
	IncludeEstimate   bool    `json:"include_estimate"`
	IncludeStock      bool    `json:"include_stock"`
	IncludeCompetitor bool    `json:"include_competitor"`
	Basis             string  `json:"basis"`       // "fob" or "ddp"
	SaleMarkup        float64 `json:"sale_markup"` // DDP multiplier; 0 defaults to 1.3
}

func (h *ExportHandler) ExportCSV(c *gin.Context) {
	var req ExportRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		RespondError(c, http.StatusUnprocessableEntity, "invalid request", err)
		return
	}

	data := h.buildExportData(&req)

	// Validate before streaming (can return JSON error)
	if len(data.Configs) == 0 || len(data.Configs[0].Items) == 0 {
		c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "no items to export"})
		return
	}

	// Get project code for filename
	projectCode := req.ProjectName // legacy field: may contain code from frontend
	if projectCode == "" && req.ProjectUUID != "" {
		if project, err := h.projectService.GetByUUID(req.ProjectUUID, h.dbUsername); err == nil && project != nil {
			projectCode = project.Code
		}
	}
	if projectCode == "" {
		projectCode = req.Name
	}

	// Set headers before streaming
	exportDate := data.CreatedAt
	articleSegment := sanitizeFilenameSegment(req.Article)
	if articleSegment == "" {
		articleSegment = "BOM"
	}
	filename := fmt.Sprintf("%s (%s) %s %s.csv", exportDate.Format("2006-01-02"), projectCode, req.Name, articleSegment)
	c.Header("Content-Type", "text/csv; charset=utf-8")
	c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))

	// Stream CSV (cannot return JSON after this point)
	if err := h.exportService.ToCSV(c.Writer, data); err != nil {
		c.Error(err) // Log only
		return
	}
}

// buildExportData converts an ExportRequest into a ProjectExportData using a temporary Configuration model
// so that ExportService.ConfigToExportData can resolve categories via localDB.
func (h *ExportHandler) buildExportData(req *ExportRequest) *services.ProjectExportData {
	configItems := make(models.ConfigItems, len(req.Items))
	for i, item := range req.Items {
		configItems[i] = models.ConfigItem{
			LotName:   item.LotName,
			Quantity:  item.Quantity,
			UnitPrice: item.UnitPrice,
		}
	}

	serverCount := req.ServerCount
	if serverCount < 1 {
		serverCount = 1
	}

	cfg := &models.Configuration{
		Article:     req.Article,
		ServerCount: serverCount,
		PricelistID: req.PricelistID,
		Items:       configItems,
		CreatedAt:   time.Now(),
	}

	return h.exportService.ConfigToExportData(cfg)
}

func sanitizeFilenameSegment(value string) string {
	if strings.TrimSpace(value) == "" {
		return ""
	}
	replacer := strings.NewReplacer(
		"/", "_",
		"\\", "_",
		":", "_",
		"*", "_",
		"?", "_",
		"\"", "_",
		"<", "_",
		">", "_",
		"|", "_",
	)
	return strings.TrimSpace(replacer.Replace(value))
}

func (h *ExportHandler) ExportConfigCSV(c *gin.Context) {
	uuid := c.Param("uuid")

	// Get config before streaming (can return JSON error)
	config, err := h.configService.GetByUUIDNoAuth(uuid)
	if err != nil {
		RespondError(c, http.StatusNotFound, "resource not found", err)
		return
	}

	data := h.exportService.ConfigToExportData(config)

	// Validate before streaming (can return JSON error)
	if len(data.Configs) == 0 || len(data.Configs[0].Items) == 0 {
		c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "no items to export"})
		return
	}

	// Get project code for filename
	projectCode := config.Name // fallback: use config name if no project
	if config.ProjectUUID != nil && *config.ProjectUUID != "" {
		if project, err := h.projectService.GetByUUID(*config.ProjectUUID, h.dbUsername); err == nil && project != nil {
			projectCode = project.Code
		}
	}

	// Set headers before streaming
	// Use price update time if available, otherwise creation time
	exportDate := config.CreatedAt
	if config.PriceUpdatedAt != nil {
		exportDate = *config.PriceUpdatedAt
	}
	filename := fmt.Sprintf("%s (%s) %s BOM.csv", exportDate.Format("2006-01-02"), projectCode, config.Name)
	c.Header("Content-Type", "text/csv; charset=utf-8")
	c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))

	// Stream CSV (cannot return JSON after this point)
	if err := h.exportService.ToCSV(c.Writer, data); err != nil {
		c.Error(err) // Log only
		return
	}
}

// ExportProjectCSV exports all active configurations of a project as a single CSV file.
func (h *ExportHandler) ExportProjectCSV(c *gin.Context) {
	projectUUID := c.Param("uuid")

	project, err := h.projectService.GetByUUID(projectUUID, h.dbUsername)
	if err != nil {
		RespondError(c, http.StatusNotFound, "resource not found", err)
		return
	}

	result, err := h.projectService.ListConfigurations(projectUUID, h.dbUsername, "active")
	if err != nil {
		RespondError(c, http.StatusInternalServerError, "internal server error", err)
		return
	}

	if len(result.Configs) == 0 {
		c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "no configurations to export"})
		return
	}

	data := h.exportService.ProjectToExportData(result.Configs)

	// Filename: YYYY-MM-DD (ProjectCode) BOM.csv
	filename := fmt.Sprintf("%s (%s) BOM.csv", time.Now().Format("2006-01-02"), project.Code)
	c.Header("Content-Type", "text/csv; charset=utf-8")
	c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))

	if err := h.exportService.ToCSV(c.Writer, data); err != nil {
		c.Error(err)
		return
	}
}

func (h *ExportHandler) ExportConfigPricingCSV(c *gin.Context) {
	uuid := c.Param("uuid")

	var req ProjectExportOptionsRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		RespondError(c, http.StatusUnprocessableEntity, "invalid request", err)
		return
	}

	config, err := h.configService.GetByUUIDNoAuth(uuid)
	if err != nil {
		RespondError(c, http.StatusNotFound, "resource not found", err)
		return
	}

	opts := services.ProjectPricingExportOptions{
		IncludeLOT:        req.IncludeLOT,
		IncludeBOM:        req.IncludeBOM,
		IncludeEstimate:   req.IncludeEstimate,
		IncludeStock:      req.IncludeStock,
		IncludeCompetitor: req.IncludeCompetitor,
		Basis:             req.Basis,
		SaleMarkup:        req.SaleMarkup,
	}

	data, err := h.exportService.ConfigToPricingExportData(config, opts)
	if err != nil {
		RespondError(c, http.StatusInternalServerError, "internal server error", err)
		return
	}

	basisLabel := "FOB"
	if strings.EqualFold(strings.TrimSpace(req.Basis), "ddp") {
		basisLabel = "DDP"
	}

	projectCode := config.Name
	if config.ProjectUUID != nil && *config.ProjectUUID != "" {
		if project, err := h.projectService.GetByUUID(*config.ProjectUUID, h.dbUsername); err == nil && project != nil {
			projectCode = project.Code
		}
	}

	filename := fmt.Sprintf("%s (%s) %s %s SPEC.csv",
		time.Now().Format("2006-01-02"),
		projectCode,
		config.Name,
		basisLabel,
	)
	c.Header("Content-Type", "text/csv; charset=utf-8")
	c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))

	if err := h.exportService.ToPricingCSV(c.Writer, data, opts); err != nil {
		c.Error(err)
	}
}

func (h *ExportHandler) ExportProjectPricingCSV(c *gin.Context) {
	projectUUID := c.Param("uuid")

	var req ProjectExportOptionsRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		RespondError(c, http.StatusUnprocessableEntity, "invalid request", err)
		return
	}

	project, err := h.projectService.GetByUUID(projectUUID, h.dbUsername)
	if err != nil {
		RespondError(c, http.StatusNotFound, "resource not found", err)
		return
	}

	result, err := h.projectService.ListConfigurations(projectUUID, h.dbUsername, "active")
	if err != nil {
		RespondError(c, http.StatusInternalServerError, "internal server error", err)
		return
	}
	if len(result.Configs) == 0 {
		c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "no configurations to export"})
		return
	}

	opts := services.ProjectPricingExportOptions{
		IncludeLOT:        req.IncludeLOT,
		IncludeBOM:        req.IncludeBOM,
		IncludeEstimate:   req.IncludeEstimate,
		IncludeStock:      req.IncludeStock,
		IncludeCompetitor: req.IncludeCompetitor,
		Basis:             req.Basis,
		SaleMarkup:        req.SaleMarkup,
	}

	data, err := h.exportService.ProjectToPricingExportData(result.Configs, opts)
	if err != nil {
		RespondError(c, http.StatusInternalServerError, "internal server error", err)
		return
	}

	basisLabel := "FOB"
	if strings.EqualFold(strings.TrimSpace(req.Basis), "ddp") {
		basisLabel = "DDP"
	}
	variantLabel := strings.TrimSpace(project.Variant)
	if variantLabel == "" {
		variantLabel = "main"
	}
	filename := fmt.Sprintf("%s (%s) %s %s.csv", time.Now().Format("2006-01-02"), project.Code, basisLabel, variantLabel)
	c.Header("Content-Type", "text/csv; charset=utf-8")
	c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))

	if err := h.exportService.ToPricingCSV(c.Writer, data, opts); err != nil {
		c.Error(err)
		return
	}
}
