package customerprofile

import (
	"errors"
	"fmt"
	stdhtml "html"
	"strconv"
	"strings"
	"system-altrak/internal/domain"
	setModule "system-altrak/internal/modules/setting"
	"system-altrak/pkg/utils"
	"time"

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

const (
	customerProfileNumberFormatKey = "cp_format"
	customerProfileResetKey        = "cp_reset"
)

var errCustomerProfileNameConflict = errors.New("customer profile with the same name already exists")
var errCustomerProfileNameRequired = errors.New("customer profile name is required")

var customerProfileDefaultSettings = map[string]string{
	customerProfileNumberFormatKey: domain.CustomerProfileDefaultNumberFormat,
	customerProfileResetKey:        domain.CustomerProfileDefaultResetMode,
}

type CustomerProfileService interface {
	SaveDraft(profile *domain.CustomerProfile, branchID uint, role string) error
	SaveLocked(profile *domain.CustomerProfile, branchID uint, role string) error
	GetProfile(id uint, branchID uint, role string) (*domain.CustomerProfile, error)
	ListProfiles(page, limit int, search, tab string, branchID uint, role string) ([]domain.CustomerProfile, int64, error)
	DeleteProfile(id uint, branchID uint, role string, userID uint, username string) error
	ExportToExcel(profiles []domain.CustomerProfile) ([]byte, error)
	ExportToPDF(profiles []domain.CustomerProfile) ([]byte, error)
	ExportHTMLToPDF(html string) ([]byte, error)
}

type serviceImpl struct {
	repo    CustomerProfileRepository
	setting setModule.SettingService
}

func (s *serviceImpl) nextDocumentNumber(tx *gorm.DB) (string, error) {
	now := time.Now()
	year, month := s.customerProfileSequenceScope(now)

	seq := domain.SerialSequence{}
	seed := domain.SerialSequence{DocType: domain.CustomerProfileSequenceDocType, Year: year, Month: month}
	if err := tx.
		Where(seed).
		FirstOrCreate(&seq, seed).Error; err != nil {
		return "", err
	}

	seq.CurrentNumber++
	if err := tx.Save(&seq).Error; err != nil {
		return "", err
	}

	return utils.GenerateDocRef(s.customerProfileNumberFormat(), seq.CurrentNumber, now), nil
}

func NewService(repo CustomerProfileRepository, setting setModule.SettingService) CustomerProfileService {
	return &serviceImpl{repo: repo, setting: setting}
}

func (s *serviceImpl) getSettingValue(key string) string {
	defaultValue := customerProfileDefaultSettings[key]

	if s.setting == nil {
		return defaultValue
	}

	value, err := s.setting.Get(key)
	if err != nil {
		return defaultValue
	}

	trimmed := strings.TrimSpace(value)
	if trimmed == "" {
		return defaultValue
	}

	return trimmed
}

func (s *serviceImpl) customerProfileSequenceScope(now time.Time) (int, int) {
	switch strings.ToLower(s.getSettingValue(customerProfileResetKey)) {
	case "monthly":
		return now.Year(), int(now.Month())
	case "yearly":
		return now.Year(), 0
	default:
		return 0, 0
	}
}

func (s *serviceImpl) customerProfileNumberFormat() string {
	return s.getSettingValue(customerProfileNumberFormatKey)
}

func customerProfileDisplayDocumentNumber(profile domain.CustomerProfile, format string) string {
	normalized := normalizeCustomerProfileDocumentNumber(profile.DocumentNumber)
	if normalized == "" {
		return "-"
	}

	parsed, err := strconv.Atoi(normalized)
	if err != nil {
		return normalized
	}

	if format == "" {
		return normalized
	}

	date := profile.CreatedAt
	if date.IsZero() {
		date = time.Now()
	}

	return utils.GenerateDocRef(format, parsed, date)
}

func decorateCustomerProfiles(profiles []domain.CustomerProfile, format string) []domain.CustomerProfile {
	for i := range profiles {
		profiles[i].DocumentNumber = customerProfileDisplayDocumentNumber(profiles[i], format)
	}
	return profiles
}

func customerProfileArchiveCustomerCell(profile domain.CustomerProfile) string {
	name := strings.TrimSpace(profile.CustomerName)
	city := strings.TrimSpace(profile.CityBilling)

	parts := make([]string, 0, 2)
	if name != "" {
		parts = append(parts, name)
	}
	if city != "" {
		parts = append(parts, city)
	}

	if len(parts) == 0 {
		return "-"
	}

	return strings.Join(parts, " ")
}

func normalizeCustomerProfileDocumentNumber(raw string) string {
	normalized := strings.TrimSpace(raw)
	if normalized == "" {
		return ""
	}

	parsed, err := strconv.Atoi(normalized)
	if err == nil && parsed > 0 {
		return fmt.Sprintf("%03d", parsed)
	}

	return normalized
}

func filterCustomerProfileDecisionMakers(items []domain.CustomerDecisionMaker) []domain.CustomerDecisionMaker {
	filtered := make([]domain.CustomerDecisionMaker, 0, len(items))
	for _, item := range items {
		item.Name = strings.TrimSpace(item.Name)
		item.Position = strings.TrimSpace(item.Position)
		item.ContactInfo = strings.TrimSpace(item.ContactInfo)
		item.Level = strings.TrimSpace(item.Level)

		if item.Name == "" {
			continue
		}

		filtered = append(filtered, item)
	}

	return filtered
}

func filterCustomerProfileEquipments(items []domain.CustomerEquipment) []domain.CustomerEquipment {
	filtered := make([]domain.CustomerEquipment, 0, len(items))
	for _, item := range items {
		item.EquipmentType = strings.TrimSpace(item.EquipmentType)
		item.Brand = strings.TrimSpace(item.Brand)
		item.Model = strings.TrimSpace(item.Model)
		item.MachineSN = strings.TrimSpace(item.MachineSN)
		item.EngineModel = strings.TrimSpace(item.EngineModel)
		item.EngineSN = strings.TrimSpace(item.EngineSN)
		if item.DeliveryDate != nil {
			copyDate := *item.DeliveryDate
			item.DeliveryDate = &copyDate
		}

		if item.Brand == "" {
			continue
		}

		filtered = append(filtered, item)
	}

	return filtered
}

func filterCustomerProfileAffiliations(items []domain.CustomerAffiliation) []domain.CustomerAffiliation {
	filtered := make([]domain.CustomerAffiliation, 0, len(items))
	for _, item := range items {
		item.Name = strings.TrimSpace(item.Name)
		item.Address = strings.TrimSpace(item.Address)

		if item.Name == "" {
			continue
		}

		filtered = append(filtered, item)
	}

	return filtered
}

func normalizeCustomerProfilePayload(profile *domain.CustomerProfile) {
	if profile == nil {
		return
	}

	profile.CustomerName = strings.TrimSpace(profile.CustomerName)
	profile.Status = strings.ToLower(strings.TrimSpace(profile.Status))
	profile.DecisionMakers = filterCustomerProfileDecisionMakers(profile.DecisionMakers)
	profile.Equipments = filterCustomerProfileEquipments(profile.Equipments)
	profile.Affiliations = filterCustomerProfileAffiliations(profile.Affiliations)
}

func normalizeCustomerProfileTab(tab string) string {
	switch strings.ToLower(strings.TrimSpace(tab)) {
	case "wapu":
		return "wapu"
	case "non_wapu":
		return "non_wapu"
	default:
		return "all"
	}
}

func applyCustomerProfileListFilters(query *gorm.DB, search, tab string, branchID uint, role string) *gorm.DB {
	if role != "superadmin" && branchID > 0 {
		query = query.Where("branch_id = ?", branchID)
	}

	if trimmedSearch := strings.TrimSpace(search); trimmedSearch != "" {
		like := "%" + trimmedSearch + "%"
		query = query.Where("(customer_name LIKE ? OR npwp LIKE ?)", like, like)
	}

	switch normalizeCustomerProfileTab(tab) {
	case "wapu":
		query = query.Where("wapu_status = ?", true)
	case "non_wapu":
		query = query.Where("wapu_status = ?", false)
	}

	return query
}

func (s *serviceImpl) SaveDraft(profile *domain.CustomerProfile, branchID uint, role string) error {
	profile.Status = "draft"
	profile.BranchID = branchID
	return s.save(profile, branchID, role)
}

func (s *serviceImpl) SaveLocked(profile *domain.CustomerProfile, branchID uint, role string) error {
	profile.Status = "locked"
	profile.BranchID = branchID
	return s.save(profile, branchID, role)
}

func (s *serviceImpl) save(profile *domain.CustomerProfile, branchID uint, role string) error {
	normalizeCustomerProfilePayload(profile)
	if strings.TrimSpace(profile.CustomerName) == "" {
		return errCustomerProfileNameRequired
	}

	return s.repo.GetDB().GetDB().Transaction(func(tx *gorm.DB) error {
		if profile.CustomerName != "" {
			var existingByName domain.CustomerProfile
			if err := tx.Where("customer_name = ?", profile.CustomerName).First(&existingByName).Error; err == nil {
				if profile.ID == 0 {
					if role != "superadmin" && branchID > 0 && existingByName.BranchID != branchID {
						return errCustomerProfileNameConflict
					}
					profile.ID = existingByName.ID
					profile.BranchID = existingByName.BranchID
				} else if existingByName.ID != profile.ID {
					return errCustomerProfileNameConflict
				}
			} else if !errors.Is(err, gorm.ErrRecordNotFound) {
				return err
			}
		}

		if profile.ID != 0 {
			var existing domain.CustomerProfile
			q := tx.Where("id = ?", profile.ID)
			if role != "superadmin" && branchID > 0 {
				q = q.Where("branch_id = ?", branchID)
			}
			if err := q.First(&existing).Error; err != nil {
				return err
			}
			profile.BranchID = existing.BranchID
			if strings.TrimSpace(existing.DocumentNumber) != "" {
				profile.DocumentNumber = existing.DocumentNumber
			}

			if err := tx.Where("user_profile_id = ?", profile.ID).Delete(&domain.CustomerDecisionMaker{}).Error; err != nil {
				return err
			}
			if err := tx.Where("user_profile_id = ?", profile.ID).Delete(&domain.CustomerEquipment{}).Error; err != nil {
				return err
			}
			if err := tx.Where("user_profile_id = ?", profile.ID).Delete(&domain.CustomerAffiliation{}).Error; err != nil {
				return err
			}

			for i := range profile.DecisionMakers {
				profile.DecisionMakers[i].ID = 0
				profile.DecisionMakers[i].UserProfileID = profile.ID
			}
			for i := range profile.Equipments {
				profile.Equipments[i].ID = 0
				profile.Equipments[i].UserProfileID = profile.ID
			}
			for i := range profile.Affiliations {
				profile.Affiliations[i].ID = 0
				profile.Affiliations[i].UserProfileID = profile.ID
			}
		} else if role != "superadmin" {
			profile.BranchID = branchID
		}

		documentNumber := normalizeCustomerProfileDocumentNumber(profile.DocumentNumber)
		if documentNumber == "" {
			generatedDocumentNumber, err := s.nextDocumentNumber(tx)
			if err != nil {
				return err
			}
			documentNumber = generatedDocumentNumber
		}
		profile.DocumentNumber = documentNumber

		if err := tx.Save(profile).Error; err != nil {
			return err
		}
		return nil
	})
}

func (s *serviceImpl) GetProfile(id uint, branchID uint, role string) (*domain.CustomerProfile, error) {
	var profile domain.CustomerProfile
	q := s.repo.GetDB().GetDB().
		Preload("DecisionMakers").
		Preload("Equipments").
		Preload("Affiliations")
	if role != "superadmin" && branchID > 0 {
		q = q.Where("branch_id = ?", branchID)
	}
	err := q.First(&profile, id).Error
	if err != nil {
		return nil, errors.New("Customer Profile not found")
	}
	profile.DocumentNumber = customerProfileDisplayDocumentNumber(profile, s.customerProfileNumberFormat())
	return &profile, nil
}

func (s *serviceImpl) ListProfiles(page, limit int, search, tab string, branchID uint, role string) ([]domain.CustomerProfile, int64, error) {
	var profiles []domain.CustomerProfile
	var total int64

	if page < 1 {
		page = 1
	}
	if limit <= 0 {
		limit = 10
	}

	query := s.repo.GetDB().GetDB().Model(&domain.CustomerProfile{})
	query = applyCustomerProfileListFilters(query, search, tab, branchID, role)

	if err := query.Session(&gorm.Session{}).Count(&total).Error; err != nil {
		return nil, 0, err
	}

	offset := (page - 1) * limit
	err := query.Order("created_at desc").Limit(limit).Offset(offset).Find(&profiles).Error
	if err != nil {
		return nil, 0, err
	}

	profiles = decorateCustomerProfiles(profiles, s.customerProfileNumberFormat())

	return profiles, total, err
}

func (s *serviceImpl) DeleteProfile(id uint, branchID uint, role string, userID uint, username string) error {
	return s.repo.GetDB().GetDB().Transaction(func(tx *gorm.DB) error {
		var profile domain.CustomerProfile
		q := tx.Where("id = ?", id)
		if role != "superadmin" && branchID > 0 {
			q = q.Where("branch_id = ?", branchID)
		}

		if err := q.First(&profile).Error; err != nil {
			return err
		}

		// Pre-delete children explicitly to ensure they are also soft-deleted
		tx.Where("user_profile_id = ?", profile.ID).Delete(&domain.CustomerDecisionMaker{})
		tx.Where("user_profile_id = ?", profile.ID).Delete(&domain.CustomerEquipment{})
		tx.Where("user_profile_id = ?", profile.ID).Delete(&domain.CustomerAffiliation{})

		// Log the action manually to ensure it hits the audit trail with context
		logEntry := &domain.ActivityLog{
			UserID:    userID,
			Username:  username,
			Role:      role,
			Module:    "customer_profile",
			Action:    "DELETE",
			Details:   fmt.Sprintf("Deleted Customer Profile: %s (%s)", profile.CustomerName, profile.DocumentNumber),
			BranchID:  branchID,
			IPAddress: "System",
		}
		tx.Create(logEntry)

		return tx.Delete(&profile).Error
	})
}

// ExportToExcel exports customer profiles to Excel format
func (s *serviceImpl) ExportToExcel(profiles []domain.CustomerProfile) ([]byte, error) {
	profiles = decorateCustomerProfiles(profiles, s.customerProfileNumberFormat())

	f := excelize.NewFile()
	sheet := "Customer Profiles"
	index, _ := f.NewSheet(sheet)
	f.SetActiveSheet(index)

	headers := []string{"No", "Date", "No Dokumen", "Customer", "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 := fmt.Sprintf("%s1", string(rune('A'+i)))
		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", 18)
	f.SetColWidth(sheet, "D", "D", 42)
	f.SetColWidth(sheet, "E", "E", 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},
	})

	for i, profile := range profiles {
		row := i + 2
		f.SetCellValue(sheet, fmt.Sprintf("A%d", row), i+1)
		f.SetCellValue(sheet, fmt.Sprintf("B%d", row), profile.CreatedAt.Format("02/01/2006"))
		f.SetCellValue(sheet, fmt.Sprintf("C%d", row), profile.DocumentNumber)
		f.SetCellValue(sheet, fmt.Sprintf("D%d", row), customerProfileArchiveCustomerCell(profile))
		f.SetCellValue(sheet, fmt.Sprintf("E%d", row), profile.NatureOfBusiness)

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

	f.AutoFilter(sheet, fmt.Sprintf("A1:E%d", len(profiles)+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
}

// ExportToPDF exports customer profiles to PDF format
func (s *serviceImpl) ExportToPDF(profiles []domain.CustomerProfile) ([]byte, error) {
	profiles = decorateCustomerProfiles(profiles, s.customerProfileNumberFormat())

	pdfGen := utils.NewPdfGenerator()

	html := `
	<!DOCTYPE html>
	<html>
	<head>
		<style>
			@page { size: A4 landscape; margin: 10mm; }
			body { font-family: 'Times New Roman', serif; font-size: 10pt; margin: 0; color: #111; }
			h1 { text-align: center; margin: 0 0 2mm 0; font-size: 16px; }
			p { text-align: center; margin: 0 0 6mm 0; font-size: 11px; }
			table { width: 100%; border-collapse: collapse; }
			th, td { border: 1px solid #cbd5e1; padding: 8px; vertical-align: top; }
			th { background-color: #0F766E; color: white; font-weight: bold; font-size: 9pt; text-transform: uppercase; }
			td { font-size: 9pt; white-space: pre-line; }
			tr:nth-child(even) { background-color: #f8fafc; }
		</style>
	</head>
	<body>
		<h1>PT. ALTRAK 1978 BANJARMASIN</h1>
		<p>Customer Profile Archives</p>
		<table>
			<thead>
				<tr>
					<th style="width: 5%;">No</th>
					<th style="width: 15%;">Date</th>
					<th style="width: 15%;">No Dokumen</th>
					<th style="width: 35%;">Customer</th>
					<th style="width: 30%;">Remark</th>
				</tr>
			</thead>
			<tbody>`

	for i, profile := range profiles {
		dateText := profile.CreatedAt.Format("02/01/2006")
		documentNumber := stdhtml.EscapeString(normalizeCustomerProfileDocumentNumber(profile.DocumentNumber))
		customerCell := stdhtml.EscapeString(customerProfileArchiveCustomerCell(profile))
		remarkCell := stdhtml.EscapeString(profile.NatureOfBusiness)
		if remarkCell == "" {
			remarkCell = "---"
		}
		html += fmt.Sprintf(`
				<tr>
					<td style="text-align:center;">%d</td>
					<td style="text-align:center; white-space:nowrap;">%s</td>
					<td style="text-align:center; white-space:nowrap;">%s</td>
					<td style="white-space:normal; word-break:break-word;">%s</td>
					<td style="white-space:normal; word-break:break-word;">%s</td>
				</tr>`, i+1, dateText, documentNumber, customerCell, remarkCell)
	}

	html += `
			</tbody>
		</table>
	</body>
	</html>`

	return pdfGen.GenerateFromHtml(html)
}

// ExportHTMLToPDF exports arbitrary printable HTML to a PDF byte buffer.
func (s *serviceImpl) ExportHTMLToPDF(html string) ([]byte, error) {
	pdfGen := utils.NewPdfGenerator()
	return pdfGen.GenerateFromHtml(html)
}
