package creditlimit

import (
	"encoding/json"
	"fmt"
	"math"
	"strings"
	"system-altrak/internal/domain"
	"system-altrak/internal/dto"
	setModule "system-altrak/internal/modules/setting"
	"system-altrak/pkg/utils"
	"time"

	"github.com/xuri/excelize/v2"
)

type CreditLimitService interface {
	Create(req *dto.CreditLimitRequest, userID, branchID uint) (*domain.CreditLimit, error)
	Update(id uint, req *dto.CreditLimitRequest, branchID uint, role string) (*domain.CreditLimit, error)
	GetByID(id uint, branchID uint, role string) (*domain.CreditLimit, error)
	List(branchID uint, role string) ([]domain.CreditLimit, error)
	GeneratePDF(id uint, branchID uint, role string) ([]byte, string, error)
	ExportExcel(branchID uint, role string) ([]byte, error)
	Delete(id uint, branchID uint, role string) error
}

type serviceImpl struct {
	repo    CreditLimitRepository
	pdf     *utils.PdfGenerator
	setting setModule.SettingService
}

func NewService(repo CreditLimitRepository, pdf *utils.PdfGenerator, set setModule.SettingService) CreditLimitService {
	return &serviceImpl{repo: repo, pdf: pdf, setting: set}
}

type UnitRow struct {
	Name   string
	Qty    string
	Status string
}

func resolveRequestedLimitAmount(req *dto.CreditLimitRequest) float64 {
	requestedLimitAmount := req.RequestedLimit
	if req.RequestedLimitRaw != "" {
		cleanedNumericString := ""
		for _, char := range req.RequestedLimitRaw {
			if char >= '0' && char <= '9' {
				cleanedNumericString += string(char)
			}
		}
		var parsedValue float64
		fmt.Sscanf(cleanedNumericString, "%f", &parsedValue)
		if parsedValue > 0 {
			requestedLimitAmount = parsedValue
		}
	}
	return requestedLimitAmount
}

func calculateCreditLimitTotals(req *dto.CreditLimitRequest) (float64, float64) {
	total := req.Spareparts + req.FilterAmount + req.Valvoline + req.Others
	agingTotal := req.AgingNotDue + req.Aging1Month + req.Aging2Months + req.Aging3Months + req.AgingOver3
	return total, agingTotal
}

func (s *serviceImpl) Create(req *dto.CreditLimitRequest, userID, branchID uint) (*domain.CreditLimit, error) {
	now := time.Now()
	year := now.Year()
	month := int(now.Month())

	seq, err := s.repo.GetNextSequence("IOM", year, month)
	if err != nil {
		return nil, fmt.Errorf("failed to get sequence: %v", err)
	}

	// Use dynamic pattern from settings
	pattern, err := s.setting.Get("cl_format")
	if err != nil || pattern == "" {
		pattern = "{INC}/IOM-BJM/{ROM}/{YY}" // Fallback
	}
	ref := utils.GenerateDocRef(pattern, seq, now)

	total, agingTotal := calculateCreditLimitTotals(req)
	requestedLimitAmount := resolveRequestedLimitAmount(req)

	signedBy := req.SignedBy
	if signedBy == "" {
		signedBy = "DWI HANDOKO"
	}
	signedTitle := req.SignedTitle
	if signedTitle == "" {
		signedTitle = "Branch Head"
	}

	cl := &domain.CreditLimit{
		Ref:             ref,
		Date:            now,
		Customer:        req.Customer,
		CustomerCode:    req.CustomerCode,
		Subject:         req.Subject,
		RequestedLimit:  requestedLimitAmount,
		BusinessLine:    req.BusinessLine,
		CompanyGroup:    req.CompanyGroup,
		UnitsFromAltrak: req.UnitsFromAltrak,
		UnitsFromOther:  req.UnitsFromOther,
		Spareparts:      req.Spareparts,
		FilterAmount:    req.FilterAmount,
		Valvoline:       req.Valvoline,
		Others:          req.Others,
		Total:           total,
		AgingAsOfDate:   req.AgingAsOfDate,
		AgingNotDue:     req.AgingNotDue,
		Aging1Month:     req.Aging1Month,
		Aging2Months:    req.Aging2Months,
		Aging3Months:    req.Aging3Months,
		AgingOver3:      req.AgingOver3,
		AgingTotal:      agingTotal,
		Cc:              req.Cc,
		SignedBy:        signedBy,
		SignedTitle:     signedTitle,
		Status:          "draft",
		CreatedBy:       &userID,
		BranchID:        branchID,
	}

	if err := s.repo.Create(cl); err != nil {
		return nil, err
	}
	return cl, nil
}

func (s *serviceImpl) Update(id uint, req *dto.CreditLimitRequest, branchID uint, role string) (*domain.CreditLimit, error) {
	cl, err := s.repo.GetByID(id, branchID, role)
	if err != nil {
		return nil, err
	}

	total, agingTotal := calculateCreditLimitTotals(req)
	requestedLimitAmount := resolveRequestedLimitAmount(req)

	cl.Customer = req.Customer
	cl.CustomerCode = req.CustomerCode
	cl.Subject = req.Subject
	cl.RequestedLimit = requestedLimitAmount
	cl.BusinessLine = req.BusinessLine
	cl.CompanyGroup = req.CompanyGroup
	cl.UnitsFromAltrak = req.UnitsFromAltrak
	cl.UnitsFromOther = req.UnitsFromOther
	cl.Spareparts = req.Spareparts
	cl.FilterAmount = req.FilterAmount
	cl.Valvoline = req.Valvoline
	cl.Others = req.Others
	cl.Total = total
	cl.AgingAsOfDate = req.AgingAsOfDate
	cl.AgingNotDue = req.AgingNotDue
	cl.Aging1Month = req.Aging1Month
	cl.Aging2Months = req.Aging2Months
	cl.Aging3Months = req.Aging3Months
	cl.AgingOver3 = req.AgingOver3
	cl.AgingTotal = agingTotal
	cl.Cc = req.Cc
	if req.SignedBy != "" {
		cl.SignedBy = req.SignedBy
	}
	if req.SignedTitle != "" {
		cl.SignedTitle = req.SignedTitle
	}
	if cl.SignedBy == "" {
		cl.SignedBy = "DWI HANDOKO"
	}
	if cl.SignedTitle == "" {
		cl.SignedTitle = "Branch Head"
	}
	cl.RevisionNumber++

	if err := s.repo.Update(cl); err != nil {
		return nil, err
	}
	return cl, nil
}

func (s *serviceImpl) GetByID(id uint, branchID uint, role string) (*domain.CreditLimit, error) {
	return s.repo.GetByID(id, branchID, role)
}

func (s *serviceImpl) List(branchID uint, role string) ([]domain.CreditLimit, error) {
	return s.repo.List(branchID, role)
}

func (s *serviceImpl) GeneratePDF(id uint, branchID uint, role string) ([]byte, string, error) {
	cl, err := s.repo.GetByID(id, branchID, role)
	if err != nil {
		return nil, "", err
	}

	dateDisplay := formatDateIndonesianShort(cl.Date)

	parseUnits := func(raw string) []UnitRow {
		if raw == "" {
			return nil
		}
		var rows []UnitRow
		_ = json.Unmarshal([]byte(raw), &rows)
		return rows
	}

	data := map[string]interface{}{
		"Ref":                cl.Ref,
		"DateDisplay":        dateDisplay,
		"CustomerName":       cl.Customer,
		"CustomerCode":       cl.CustomerCode,
		"CompanyGroup":       cl.CompanyGroup,
		"BusinessLine":       cl.BusinessLine,
		"RequestedLimitText": formatRupiah(cl.RequestedLimit),
		"SparepartsText":     formatRupiah(cl.Spareparts),
		"FilterText":         formatRupiah(cl.FilterAmount),
		"ValvolineText":      formatRupiah(cl.Valvoline),
		"OthersText":         formatRupiah(cl.Others),
		"PotentialTotalText": formatRupiah(cl.Total),
		"AgingAsOfDate":      cl.AgingAsOfDate,
		"AgingNotDueText":    formatRupiah(cl.AgingNotDue),
		"Aging1MonthText":    formatRupiah(cl.Aging1Month),
		"Aging2MonthsText":   formatRupiah(cl.Aging2Months),
		"Aging3MonthsText":   formatRupiah(cl.Aging3Months),
		"AgingOver3Text":     formatRupiah(cl.AgingOver3),
		"AgingTotalText":     formatRupiah(cl.AgingTotal),
		"Cc":                 cl.Cc,
		"SignatureName":      cl.SignedBy,
		"SignatureTitle":     cl.SignedTitle,
		"UnitsAltrak":        parseUnits(cl.UnitsFromAltrak),
		"UnitsOther":         parseUnits(cl.UnitsFromOther),
	}

	helper := utils.NewExportHelper(s.pdf, s.setting)
	buf, err := helper.GenerateStandardPDF("iom/credit_limit_pdf.html", data)
	if err != nil {
		return nil, "", err
	}

	return buf, cl.Ref, nil
}

func formatRupiah(v float64) string {
	if v == 0 {
		return "0"
	}
	intVal := int64(math.Round(v))
	negative := intVal < 0
	if negative {
		intVal = -intVal
	}
	s := fmt.Sprintf("%d", intVal)
	result := ""
	for i, c := range s {
		if i > 0 && (len(s)-i)%3 == 0 {
			result += "."
		}
		result += string(c)
	}
	if negative {
		return "-" + result
	}
	return result
}

func formatDateIndonesianShort(t time.Time) string {
	if t.IsZero() {
		return "-"
	}

	months := []string{"", "Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember"}
	monthIndex := int(t.Month())
	if monthIndex < 1 || monthIndex >= len(months) {
		return t.Format("02-01-2006")
	}

	return fmt.Sprintf("%02d %s %d", t.Day(), months[monthIndex], t.Year())
}

func (s *serviceImpl) ExportExcel(branchID uint, role string) ([]byte, error) {
	list, err := s.repo.List(branchID, role)
	if err != nil {
		return nil, err
	}

	f := excelize.NewFile()
	defer f.Close()

	sheet := "Credit Limit IOM"
	index, _ := f.NewSheet(sheet)
	f.SetActiveSheet(index)
	f.DeleteSheet("Sheet1")

	headers := []string{"No Dokumen", "Tanggal", "Pelanggan", "Kode", "Batas Kredit", "Total Potensi", "Total Aging", "Status"}
	headerStyle, _ := f.NewStyle(&excelize.Style{
		Font:      &excelize.Font{Bold: true, Size: 11, Color: "#FFFFFF"},
		Fill:      excelize.Fill{Type: "pattern", Color: []string{"#0F766E"}, Pattern: 1},
		Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
		Border: []excelize.Border{
			{Type: "left", Color: "#FFFFFF", Style: 1},
			{Type: "top", Color: "#FFFFFF", Style: 1},
			{Type: "bottom", Color: "#FFFFFF", Style: 1},
			{Type: "right", Color: "#FFFFFF", Style: 1},
		},
	})

	for i, h := range headers {
		cell, _ := excelize.CoordinatesToCellName(i+1, 1)
		f.SetCellValue(sheet, cell, h)
		f.SetCellStyle(sheet, cell, cell, headerStyle)
	}
	f.SetRowHeight(sheet, 1, 24)
	
	f.SetColWidth(sheet, "A", "A", 24) // Ref
	f.SetColWidth(sheet, "B", "B", 14) // Date
	f.SetColWidth(sheet, "C", "C", 32) // Customer
	f.SetColWidth(sheet, "D", "D", 12) // Code
	f.SetColWidth(sheet, "E", "E", 18) // Req Limit
	f.SetColWidth(sheet, "F", "F", 18) // Total Potential
	f.SetColWidth(sheet, "G", "G", 18) // Total Aging
	f.SetColWidth(sheet, "H", "H", 14) // Status

	dataStyle, _ := f.NewStyle(&excelize.Style{
		Border: []excelize.Border{
			{Type: "left", Color: "#E2E8F0", Style: 1},
			{Type: "top", Color: "#E2E8F0", Style: 1},
			{Type: "bottom", Color: "#E2E8F0", Style: 1},
			{Type: "right", Color: "#E2E8F0", Style: 1},
		},
		Alignment: &excelize.Alignment{Vertical: "center"},
	})
	
	currencyStyle, _ := f.NewStyle(&excelize.Style{
		Border: []excelize.Border{
			{Type: "left", Color: "#E2E8F0", Style: 1},
			{Type: "top", Color: "#E2E8F0", Style: 1},
			{Type: "bottom", Color: "#E2E8F0", Style: 1},
			{Type: "right", Color: "#E2E8F0", Style: 1},
		},
		Alignment:    &excelize.Alignment{Horizontal: "right", Vertical: "center"},
		CustomNumFmt: &[]string{"#,##0"}[0],
	})

	for i, item := range list {
		row := i + 2
		f.SetCellValue(sheet, fmt.Sprintf("A%d", row), item.Ref)
		f.SetCellValue(sheet, fmt.Sprintf("B%d", row), item.Date.Format("02/01/2006"))
		f.SetCellValue(sheet, fmt.Sprintf("C%d", row), item.Customer)
		f.SetCellValue(sheet, fmt.Sprintf("D%d", row), item.CustomerCode)
		f.SetCellValue(sheet, fmt.Sprintf("E%d", row), item.RequestedLimit)
		f.SetCellValue(sheet, fmt.Sprintf("F%d", row), item.Total)
		f.SetCellValue(sheet, fmt.Sprintf("G%d", row), item.AgingTotal)
		f.SetCellValue(sheet, fmt.Sprintf("H%d", row), strings.ToUpper(item.Status))

		for col := 'A'; col <= 'D'; col++ {
			cell := fmt.Sprintf("%c%d", col, row)
			f.SetCellStyle(sheet, cell, cell, dataStyle)
		}
		
		for col := 'E'; col <= 'G'; col++ {
			cell := fmt.Sprintf("%c%d", col, row)
			f.SetCellStyle(sheet, cell, cell, currencyStyle)
		}
		
		f.SetCellStyle(sheet, fmt.Sprintf("H%d", row), fmt.Sprintf("H%d", row), dataStyle)
		f.SetRowHeight(sheet, row, 20)
	}

	f.AutoFilter(sheet, fmt.Sprintf("A1:H%d", len(list)+1), nil)
	f.SetPanes(sheet, &excelize.Panes{Freeze: true, YSplit: 1})

	buf, err := f.WriteToBuffer()
	if err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

func (s *serviceImpl) Delete(id uint, branchID uint, role string) error {
	return s.repo.Delete(id, branchID, role)
}
