package customerprofile

import (
	"errors"
	"fmt"
	"regexp"
	"strings"
	"system-altrak/internal/domain"
	"system-altrak/internal/middleware"
	"system-altrak/pkg/utils"
	"time"

	"github.com/gofiber/fiber/v2"
	"gorm.io/gorm"
)

type Handler struct {
	service CustomerProfileService
}

type exportPreviewPDFRequest struct {
	HTML string `json:"html"`
}

var previewHTMLDangerPatterns = []*regexp.Regexp{
	regexp.MustCompile(`(?is)<\s*/?\s*script\b[^>]*>`),
	regexp.MustCompile(`(?is)\bon[a-z0-9_-]+\s*=`),
	regexp.MustCompile(`(?is)javascript\s*:`),
}

const maxPreviewHTMLBytes = 1 << 20

func validatePreviewHTML(html string) (string, error) {
	sanitized := strings.TrimSpace(html)
	if sanitized == "" {
		return "", errors.New("HTML content is required")
	}

	if len(sanitized) > maxPreviewHTMLBytes {
		return "", errors.New("HTML content is too large")
	}

	for _, pattern := range previewHTMLDangerPatterns {
		if pattern.MatchString(sanitized) {
			return "", errors.New("HTML content contains unsafe markup")
		}
	}

	return sanitized, nil
}

func NewHandler(s CustomerProfileService) *Handler {
	return &Handler{service: s}
}

func (h *Handler) SaveProfile(c *fiber.Ctx) error {
	var body domain.CustomerProfile
	if err := c.BodyParser(&body); err != nil {
		return utils.ErrorResponse(c, fiber.StatusBadRequest, "Invalid JSON format")
	}

	middleware.SanitizeStruct(&body)
	normalizeCustomerProfilePayload(&body)

	mode := strings.ToLower(strings.TrimSpace(c.Query("mode", "locked")))
	if mode == "completed" {
		mode = "locked"
	}

	if normalizedStatus := strings.ToLower(strings.TrimSpace(body.Status)); normalizedStatus == "completed" {
		body.Status = "locked"
	} else if normalizedStatus == "" {
		body.Status = mode
	}

	// Use new detailed validation for both draft and locked saves so blank names
	// never reach the database and violate the unique customer_name constraint.
	result := utils.ValidateStructDetailed(body)
	if !result.IsValid {
		// Log detailed validation errors for debugging
		fmt.Printf("[customer-profile] Validation failed for %s: %+v\n", body.CustomerName, result.Errors)
		return utils.ValidationErrorResponse(c, "Validation failed", result.Errors)
	}

	if c.Params("id") != "" {
		id, err := utils.ParseParamID(c, "id")
		if err != nil {
			return utils.ErrorResponse(c, fiber.StatusBadRequest, "Invalid ID parameter")
		}
		body.ID = id
	}

	branchID, _ := c.Locals("current_branch_id").(uint)
	role, _ := c.Locals("role").(string)
	var err error
	if mode == "draft" {
		err = h.service.SaveDraft(&body, branchID, role)
	} else {
		err = h.service.SaveLocked(&body, branchID, role)
	}

	if err != nil {
		if errors.Is(err, errCustomerProfileNameRequired) {
			return utils.ErrorResponse(c, fiber.StatusBadRequest, "Customer Name is required")
		}
		if errors.Is(err, errCustomerProfileNameConflict) {
			return utils.ConflictResponse(c, "Customer Profile dengan nama yang sama sudah ada")
		}
		return utils.InternalErrorResponse(c, err.Error())
	}
	return utils.SuccessResponse(c, "Customer Profile synchronization complete", body)
}

func (h *Handler) GetProfile(c *fiber.Ctx) error {
	id, err := utils.ParseParamID(c, "id")
	if err != nil {
		return utils.ErrorResponse(c, fiber.StatusBadRequest, "Invalid ID parameter")
	}
	branchID, _ := c.Locals("current_branch_id").(uint)
	role, _ := c.Locals("role").(string)
	doc, err := h.service.GetProfile(id, branchID, role)
	if err != nil {
		return utils.NotFoundResponse(c, "Customer record")
	}
	return utils.SuccessResponse(c, "Customer Profile localized successfully", doc)
}

func (h *Handler) DeleteProfile(c *fiber.Ctx) error {
	id, err := utils.ParseParamID(c, "id")
	if err != nil {
		return utils.ErrorResponse(c, fiber.StatusBadRequest, "Invalid ID parameter")
	}
	branchID, _ := c.Locals("current_branch_id").(uint)
	role, _ := c.Locals("role").(string)
	userID, _ := c.Locals("user_id").(uint)
	username, _ := c.Locals("username").(string)

	if err := h.service.DeleteProfile(id, branchID, role, userID, username); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return utils.NotFoundResponse(c, "Customer record")
		}
		return utils.InternalErrorResponse(c, "Failed to purge customer profile")
	}
	return utils.SuccessResponse(c, "Profile purged successfully", nil)
}

func (h *Handler) ListProfiles(c *fiber.Ctx) error {
	page := c.QueryInt("page", 1)
	limit := c.QueryInt("limit", 10)
	search := c.Query("search", "")
	tab := c.Query("tab", "all")
	branchID, _ := c.Locals("current_branch_id").(uint)
	role, _ := c.Locals("role").(string)

	profiles, total, err := h.service.ListProfiles(page, limit, search, tab, branchID, role)
	if err != nil {
		return utils.InternalErrorResponse(c, "Failed to load profiles")
	}

	// Ensure total is non-negative to avoid pagination error 400
	if total < 0 {
		total = 0
	}

	pagination, err := utils.NewPagination(page, limit, total)
	if err != nil {
		// Fallback to basic pagination if utility fails
		return utils.PaginatedSuccessResponse(c, "List Customer Profile (fallback)", profiles, fiber.Map{
			"page": page,
			"limit": limit,
			"total_rows": total,
		})
	}
	pagination.SetData(profiles)

	return utils.PaginatedSuccessResponse(c, "List Customer Profile", profiles, pagination.GetMeta())
}

// ExportExcel exports customer profiles to Excel format
func (h *Handler) ExportExcel(c *fiber.Ctx) error {
	page := c.QueryInt("page", 1)
	limit := c.QueryInt("limit", 10)
	search := c.Query("search", "")
	tab := c.Query("tab", "all")
	branchID, _ := c.Locals("current_branch_id").(uint)
	role, _ := c.Locals("role").(string)

	profiles, _, err := h.service.ListProfiles(page, limit, search, tab, branchID, role)
	if err != nil {
		return utils.InternalErrorResponse(c, "Failed to load profiles")
	}

	buf, err := h.service.ExportToExcel(profiles)
	if err != nil {
		return utils.InternalErrorResponse(c, "Failed to generate Excel: "+err.Error())
	}

	c.Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
	c.Set("Content-Disposition", fmt.Sprintf("attachment; filename=Customer-Profiles-%s.xlsx", time.Now().Format("20060102")))
	return c.Send(buf)
}

// ExportPDF exports customer profiles to PDF format
func (h *Handler) ExportPDF(c *fiber.Ctx) error {
	page := c.QueryInt("page", 1)
	limit := c.QueryInt("limit", 10)
	search := c.Query("search", "")
	tab := c.Query("tab", "all")
	branchID, _ := c.Locals("current_branch_id").(uint)
	role, _ := c.Locals("role").(string)

	profiles, _, err := h.service.ListProfiles(page, limit, search, tab, branchID, role)
	if err != nil {
		return utils.InternalErrorResponse(c, "Failed to load profiles")
	}

	buf, err := h.service.ExportToPDF(profiles)
	if err != nil {
		return utils.InternalErrorResponse(c, "Failed to generate PDF: "+err.Error())
	}

	c.Set("Content-Type", "application/pdf")
	c.Set("Content-Disposition", fmt.Sprintf("attachment; filename=Customer-Profiles-%s.pdf", time.Now().Format("20060102")))
	return c.Send(buf)
}

// ExportPreviewPDF exports the live preview HTML into a downloadable PDF file.
func (h *Handler) ExportPreviewPDF(c *fiber.Ctx) error {
	var req exportPreviewPDFRequest
	if err := c.BodyParser(&req); err != nil {
		return utils.ErrorResponse(c, fiber.StatusBadRequest, "Invalid JSON format")
	}

	html, err := validatePreviewHTML(req.HTML)
	if err != nil {
		return utils.ErrorResponse(c, fiber.StatusBadRequest, err.Error())
	}

	buf, err := h.service.ExportHTMLToPDF(html)
	if err != nil {
		return utils.InternalErrorResponse(c, "Failed to generate PDF: "+err.Error())
	}

	c.Set("Content-Type", "application/pdf")
	c.Set("Content-Disposition", fmt.Sprintf("attachment; filename=Customer-Profile-Preview-%s.pdf", time.Now().Format("20060102")))
	return c.Send(buf)
}
