package serviceparts

import (
	"fmt"
	"io"
	"sort"
	"strings"
	"system-altrak/internal/domain"
	setModule "system-altrak/internal/modules/setting"
	"system-altrak/pkg/utils"
	"time"

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

type ServicePartsService interface {
	CreateAuthorization(parts *domain.MemoServiceParts, branchID uint) error
	UpdateAuthorization(parts *domain.MemoServiceParts, branchID uint, role string) error
	GetServiceParts(id uint, branchID uint, role string) (*domain.MemoServiceParts, error)
	ListServiceParts(branchID uint, role string) ([]domain.MemoServiceParts, error)
	ExportSRToExcel(branchID uint, role string, tab string, search string) (io.Reader, error)
	GeneratePDF(id uint, branchID uint, role string) ([]byte, string, error)
	DeleteServiceParts(id uint, branchID uint, role string) error
}

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

func NewService(repo ServicePartsRepository, pdf *utils.PdfGenerator, setting ...setModule.SettingService) ServicePartsService {
	var resolvedSetting setModule.SettingService
	if len(setting) > 0 {
		resolvedSetting = setting[0]
	}

	return &serviceImpl{repo: repo, pdf: pdf, setting: resolvedSetting}
}

func (s *serviceImpl) CreateAuthorization(parts *domain.MemoServiceParts, branchID uint) error {
	now := time.Now()
	nextSeq, err := s.repo.GetNextSequence("IOM", now.Year(), int(now.Month()))
	if err != nil {
		return fmt.Errorf("failed to generate requisition sequence: %w", err)
	}
	parts.Ref = utils.GenerateDocRef("{INC}/IOM-BJM/{ROM}/{YY}", nextSeq, now)
	parts.Date = now
	if parts.To == "" {
		parts.To = "Service Dept – PT.A Banjarmasin"
	}
	if parts.From == "" {
		parts.From = "Parts Dept – PT.A Banjarmasin"
	}
	parts.Status = "draft"
	parts.BranchID = branchID

	return s.repo.Create(parts)
}

func (s *serviceImpl) UpdateAuthorization(parts *domain.MemoServiceParts, branchID uint, role string) error {
	if parts.ID == 0 {
		return fmt.Errorf("invalid service requisition id")
	}
	if parts.Status == "" {
		parts.Status = "draft"
	} else {
		parts.Status = utils.NormalizeStatus("serviceparts", parts.Status)
	}
	return s.repo.Update(parts, branchID, role)
}

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

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

func normalizeSRTab(tab string) string {
	switch strings.ToLower(strings.TrimSpace(tab)) {
	case "draft":
		return "draft"
	case "completed":
		return "completed"
	case "verified":
		return "completed"
	default:
		return "all"
	}
}

func normalizeSRStatus(status string) string {
	return utils.NormalizeStatus("serviceparts", status)
}

func matchesSRTab(status string, tab string) bool {
	status = normalizeSRStatus(status)

	switch tab {
	case "draft":
		return status == "" || status == "draft"
	case "completed":
		return status == "submitted" || status == "approved" || status == "archived"
	default:
		return true
	}
}

func matchesSRSearch(parts domain.MemoServiceParts, keyword string) bool {
	if keyword == "" {
		return true
	}

	needle := strings.ToLower(strings.TrimSpace(keyword))
	if needle == "" {
		return true
	}

	fields := []string{
		parts.Ref,
		parts.Customer,
		parts.Description,
		parts.ChargeTo,
		parts.Status,
		parts.Date.Format("2006-01-02"),
		fmt.Sprintf("%d", parts.ID),
	}

	for _, field := range fields {
		if strings.Contains(strings.ToLower(field), needle) {
			return true
		}
	}

	return false
}

func (s *serviceImpl) ExportSRToExcel(branchID uint, role string, tab string, search string) (io.Reader, error) {
	srs, err := s.repo.List(branchID, role)
	if err != nil {
		return nil, err
	}

	normalizedTab := normalizeSRTab(tab)
	filtered := make([]domain.MemoServiceParts, 0, len(srs))
	for _, parts := range srs {
		if !matchesSRTab(parts.Status, normalizedTab) {
			continue
		}

		if !matchesSRSearch(parts, search) {
			continue
		}

		filtered = append(filtered, parts)
	}

	sort.SliceStable(filtered, func(i, j int) bool {
		return filtered[i].Date.After(filtered[j].Date)
	})

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

	sheet := "Service Requisition"
	index, _ := f.NewSheet(sheet)
	f.SetActiveSheet(index)
	f.DeleteSheet("Sheet1")

	headers := []string{"No", "Date", "No Dokumen", "Customer", "Category", "Remark"}
	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", 8)
	f.SetColWidth(sheet, "B", "B", 14)
	f.SetColWidth(sheet, "C", "C", 24)
	f.SetColWidth(sheet, "D", "D", 36)
	f.SetColWidth(sheet, "E", "E", 32)
	f.SetColWidth(sheet, "F", "F", 42)

	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: "top", WrapText: true},
	})

	rowIdx := 2
	for index, parts := range filtered {
		displayDate := "-"
		if !parts.Date.IsZero() {
			displayDate = parts.Date.Format("02/01/2006")
		}

		docNo := strings.TrimSpace(parts.Ref)
		if docNo == "" {
			fallbackDate := parts.Date
			if fallbackDate.IsZero() {
				fallbackDate = time.Now()
			}
			docNo = fmt.Sprintf("%03d/IOM-BJM/%s/%02d", parts.ID, utils.GetRomanMonth(int(fallbackDate.Month())), fallbackDate.Year()%100)
		}

		remark := strings.TrimSpace(parts.Description)
		if remark == "" {
			remark = strings.TrimSpace(parts.ChargeTo)
		}
		if remark == "" {
			remark = "-"
		}

		f.SetCellValue(sheet, fmt.Sprintf("A%d", rowIdx), fmt.Sprintf("%03d", index+1))
		f.SetCellValue(sheet, fmt.Sprintf("B%d", rowIdx), displayDate)
		f.SetCellValue(sheet, fmt.Sprintf("C%d", rowIdx), docNo)
		f.SetCellValue(sheet, fmt.Sprintf("D%d", rowIdx), parts.Customer)
		f.SetCellValue(sheet, fmt.Sprintf("E%d", rowIdx), "MEMO SERVICE PARTS")
		f.SetCellValue(sheet, fmt.Sprintf("F%d", rowIdx), remark)

		for col := 'A'; col <= 'F'; col++ {
			cell := fmt.Sprintf("%c%d", col, rowIdx)
			f.SetCellStyle(sheet, cell, cell, dataStyle)
		}
		f.SetRowHeight(sheet, rowIdx, 32)
		rowIdx++
	}

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

	return f.WriteToBuffer()
}

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

	if parts.Checklist == nil {
		parts.Checklist = &domain.MemoServicePartsChecklist{}
	}
	if parts.Equipments == nil {
		parts.Equipments = []domain.MemoServicePartsEquipment{}
	}

	dateStr := parts.Date.Format("02 January 2006")

	var eq1, eq2 []domain.MemoServicePartsEquipment
	for _, eq := range parts.Equipments {
		if eq.TableBlock == 2 {
			eq2 = append(eq2, eq)
		} else {
			eq1 = append(eq1, eq)
		}
	}
	parts.Equipments = eq1

	data := map[string]interface{}{
		"Header": map[string]interface{}{
			"Title":    "INTER OFFICE MEMORANDUM",
			"Subtitle": "Inventory Assets – Spareparts Division",
			"Ref":      parts.Ref,
		},
		"SR":      parts,
		"DateStr": dateStr,
	}

	if len(eq2) > 0 {
		data["SR2"] = map[string]interface{}{"Equipments": eq2}
	}

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

	return buf, parts.Ref, nil
}

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