package customerprofile

import (
	"bytes"
	"errors"
	"testing"
	"time"

	"system-altrak/internal/domain"
	setModule "system-altrak/internal/modules/setting"
	"system-altrak/internal/repository"
	"system-altrak/pkg/utils"

	"github.com/glebarez/sqlite"
	"github.com/xuri/excelize/v2"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

func setupCustomerProfileServiceTestDB(t *testing.T) *gorm.DB {
	t.Helper()

	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Silent),
	})
	if err != nil {
		t.Fatalf("open sqlite db: %v", err)
	}

	err = db.AutoMigrate(
		&domain.CustomerProfile{},
		&domain.CustomerDecisionMaker{},
		&domain.CustomerEquipment{},
		&domain.CustomerAffiliation{},
		&domain.SerialSequence{},
		&domain.SystemSetting{},
	)
	if err != nil {
		t.Fatalf("migrate customer profile schema: %v", err)
	}

	return db
}

func newCustomerProfileTestService(db *gorm.DB, setting setModule.SettingService) CustomerProfileService {
	return NewService(NewRepository(repository.NewRepository(db)), setting)
}

func TestSaveDraftUpdatesExistingProfileByName(t *testing.T) {
	db := setupCustomerProfileServiceTestDB(t)
	svc := newCustomerProfileTestService(db, nil)

	existing := &domain.CustomerProfile{
		DocumentNumber:   "007",
		CustomerName:     "PT Atlas",
		BillingAddress:   "Old Billing",
		CityBilling:      "Banjarmasin",
		Telephone:        "08123456789",
		Fax:              "021-000000",
		PurchasingEmail:  "old@example.com",
		FinanceEmail:     "finance@example.com",
		WapuStatus:       false,
		DeliveryAddress:  "Old Delivery",
		CityDelivery:     "Banjarmasin",
		Npwp:             "01.234.567.8-901.000",
		ParentCompany:    "Parent Old",
		NatureOfBusiness: "Mining",
		Status:           "locked",
		BranchID:         3,
	}
	if err := db.Create(existing).Error; err != nil {
		t.Fatalf("seed existing profile: %v", err)
	}

	update := &domain.CustomerProfile{
		CustomerName:     "PT Atlas",
		BillingAddress:   "New Billing",
		CityBilling:      "Balikpapan",
		Telephone:        "08129876543",
		Fax:              "021-111111",
		PurchasingEmail:  "new@example.com",
		FinanceEmail:     "new-finance@example.com",
		WapuStatus:       true,
		DeliveryAddress:  "New Delivery",
		CityDelivery:     "Balikpapan",
		Npwp:             "01.234.567.8-901.000",
		ParentCompany:    "Parent New",
		NatureOfBusiness: "Construction",
	}

	if err := svc.SaveDraft(update, 3, "admin"); err != nil {
		t.Fatalf("save draft should update existing profile: %v", err)
	}

	var profiles []domain.CustomerProfile
	if err := db.Order("id asc").Find(&profiles).Error; err != nil {
		t.Fatalf("load profiles: %v", err)
	}
	if len(profiles) != 1 {
		t.Fatalf("expected one profile after upsert, got %d", len(profiles))
	}

	saved := profiles[0]
	if saved.ID != existing.ID {
		t.Fatalf("expected update to reuse existing id %d, got %d", existing.ID, saved.ID)
	}
	if saved.Status != "draft" {
		t.Fatalf("expected status draft, got %q", saved.Status)
	}
	if saved.DocumentNumber != "007" {
		t.Fatalf("expected document number to be preserved, got %q", saved.DocumentNumber)
	}
	if saved.BillingAddress != "New Billing" || saved.CityBilling != "Balikpapan" || !saved.WapuStatus {
		t.Fatalf("profile was not updated correctly: %+v", saved)
	}
}

func TestSaveDraftRejectsEmptyCustomerName(t *testing.T) {
	db := setupCustomerProfileServiceTestDB(t)
	svc := newCustomerProfileTestService(db, nil)

	profile := newCustomerProfileFixture("")
	profile.CustomerName = "   "

	if err := svc.SaveDraft(profile, 3, "admin"); !errors.Is(err, errCustomerProfileNameRequired) {
		t.Fatalf("expected customer name required error, got %v", err)
	}

	var count int64
	if err := db.Model(&domain.CustomerProfile{}).Count(&count).Error; err != nil {
		t.Fatalf("count profiles: %v", err)
	}
	if count != 0 {
		t.Fatalf("expected no profiles inserted, got %d", count)
	}
}

func TestSaveDraftReturnsConflictOnDuplicateNameAcrossBranch(t *testing.T) {
	db := setupCustomerProfileServiceTestDB(t)
	svc := newCustomerProfileTestService(db, nil)

	existing := newCustomerProfileFixture("PT Atlas")
	existing.DocumentNumber = "011"
	existing.BranchID = 2
	if err := db.Create(existing).Error; err != nil {
		t.Fatalf("seed existing profile: %v", err)
	}

	duplicate := newCustomerProfileFixture("PT Atlas")
	if err := svc.SaveDraft(duplicate, 3, "admin"); !errors.Is(err, errCustomerProfileNameConflict) {
		t.Fatalf("expected duplicate-name conflict, got %v", err)
	}

	var count int64
	if err := db.Model(&domain.CustomerProfile{}).Count(&count).Error; err != nil {
		t.Fatalf("count profiles: %v", err)
	}
	if count != 1 {
		t.Fatalf("expected one profile after conflict, got %d", count)
	}
}

func TestSaveDraftAssignsSequentialDocumentNumbers(t *testing.T) {
	db := setupCustomerProfileServiceTestDB(t)
	svc := newCustomerProfileTestService(db, nil)
	now := time.Now()
	expectedFirst := utils.GenerateDocRef(domain.CustomerProfileDefaultNumberFormat, 1, now)
	expectedSecond := utils.GenerateDocRef(domain.CustomerProfileDefaultNumberFormat, 2, now)

	first := newCustomerProfileFixture("PT One")
	if err := svc.SaveDraft(first, 3, "admin"); err != nil {
		t.Fatalf("save first draft: %v", err)
	}
	if first.DocumentNumber != expectedFirst {
		t.Fatalf("expected first document number %s, got %q", expectedFirst, first.DocumentNumber)
	}

	second := newCustomerProfileFixture("PT Two")
	if err := svc.SaveDraft(second, 3, "admin"); err != nil {
		t.Fatalf("save second draft: %v", err)
	}
	if second.DocumentNumber != expectedSecond {
		t.Fatalf("expected second document number %s, got %q", expectedSecond, second.DocumentNumber)
	}
}

func TestSaveDraftNormalizesProvidedDocumentNumber(t *testing.T) {
	db := setupCustomerProfileServiceTestDB(t)
	svc := newCustomerProfileTestService(db, nil)

	profile := newCustomerProfileFixture("PT Normalize")
	profile.DocumentNumber = "7"

	if err := svc.SaveDraft(profile, 3, "admin"); err != nil {
		t.Fatalf("save draft with provided doc number: %v", err)
	}

	if profile.DocumentNumber != "007" {
		t.Fatalf("expected normalized document number 007, got %q", profile.DocumentNumber)
	}
}

func TestCustomerProfileDefaultsWithoutSettings(t *testing.T) {
	db := setupCustomerProfileServiceTestDB(t)
	svc := newCustomerProfileTestService(db, nil)
	impl := svc.(*serviceImpl)

	if got := impl.customerProfileNumberFormat(); got != domain.CustomerProfileDefaultNumberFormat {
		t.Fatalf("expected default cp_format %s, got %q", domain.CustomerProfileDefaultNumberFormat, got)
	}

	year, month := impl.customerProfileSequenceScope(time.Date(2026, time.April, 18, 0, 0, 0, 0, time.UTC))
	if year != 0 || month != 0 {
		t.Fatalf("expected global sequence scope fallback 0/0, got %d/%d", year, month)
	}
}

func TestExportToExcelUsesArchiveColumns(t *testing.T) {
	db := setupCustomerProfileServiceTestDB(t)
	svc := newCustomerProfileTestService(db, nil)

	raw, err := svc.ExportToExcel([]domain.CustomerProfile{{
		BaseModel:      domain.BaseModel{CreatedAt: time.Date(2026, time.April, 13, 0, 0, 0, 0, time.UTC)},
		DocumentNumber: "007",
		CustomerName:   "PT Atlas",
		CityBilling:    "Banjarmasin",
	}})
	if err != nil {
		t.Fatalf("export excel: %v", err)
	}

	book, err := excelize.OpenReader(bytes.NewReader(raw))
	if err != nil {
		t.Fatalf("open workbook: %v", err)
	}

	if got, _ := book.GetCellValue("Customer Profiles", "A1"); got != "No" {
		t.Fatalf("expected header No, got %q", got)
	}
	if got, _ := book.GetCellValue("Customer Profiles", "B1"); got != "Date" {
		t.Fatalf("expected header Date, got %q", got)
	}
	if got, _ := book.GetCellValue("Customer Profiles", "C1"); got != "No Dokumen" {
		t.Fatalf("expected header No Dokumen, got %q", got)
	}
	if got, _ := book.GetCellValue("Customer Profiles", "D1"); got != "Customer" {
		t.Fatalf("expected header Customer, got %q", got)
	}
	if got, _ := book.GetCellValue("Customer Profiles", "B2"); got != "13/04/2026" {
		t.Fatalf("expected date cell 13/04/2026, got %q", got)
	}
	if got, _ := book.GetCellValue("Customer Profiles", "C2"); got != utils.GenerateDocRef(domain.CustomerProfileDefaultNumberFormat, 7, time.Date(2026, time.April, 13, 0, 0, 0, 0, time.UTC)) {
		t.Fatalf("expected document number %s, got %q", utils.GenerateDocRef(domain.CustomerProfileDefaultNumberFormat, 7, time.Date(2026, time.April, 13, 0, 0, 0, 0, time.UTC)), got)
	}
	if got, _ := book.GetCellValue("Customer Profiles", "D2"); got != "PT Atlas Banjarmasin" {
		t.Fatalf("expected customer cell with name and city on one line, got %q", got)
	}
}

func TestDeleteProfileRemovesAssociatedRecords(t *testing.T) {
	db := setupCustomerProfileServiceTestDB(t)
	svc := newCustomerProfileTestService(db, nil)

	profile := newCustomerProfileFixture("PT Delete Me")
	profile.DocumentNumber = "009"
	profile.BranchID = 3
	if err := db.Create(profile).Error; err != nil {
		t.Fatalf("seed profile: %v", err)
	}

	if err := db.Create(&domain.CustomerDecisionMaker{
		UserProfileID: profile.ID,
		Name:          "Director",
		Position:      "President Director",
		ContactInfo:   "08123456789",
		Level:         "Management",
	}).Error; err != nil {
		t.Fatalf("seed decision maker: %v", err)
	}
	if err := db.Create(&domain.CustomerEquipment{
		UserProfileID: profile.ID,
		EquipmentType: "altrak",
		Brand:         "CAT",
		Model:         "320",
		MachineSN:     "SN-001",
		EngineModel:   "Engine-X",
		EngineSN:      "ENG-001",
	}).Error; err != nil {
		t.Fatalf("seed equipment: %v", err)
	}
	if err := db.Create(&domain.CustomerAffiliation{
		UserProfileID: profile.ID,
		Name:          "Affiliate",
		Address:       "Jl. Testing",
	}).Error; err != nil {
		t.Fatalf("seed affiliation: %v", err)
	}

	if err := svc.DeleteProfile(profile.ID, 3, "admin", 1, "admin"); err != nil {
		t.Fatalf("delete profile: %v", err)
	}

	var profileCount int64
	if err := db.Model(&domain.CustomerProfile{}).Where("id = ?", profile.ID).Count(&profileCount).Error; err != nil {
		t.Fatalf("count profile: %v", err)
	}
	if profileCount != 0 {
		t.Fatalf("expected profile to be deleted, count=%d", profileCount)
	}

	var childCount int64
	if err := db.Model(&domain.CustomerDecisionMaker{}).Where("user_profile_id = ?", profile.ID).Count(&childCount).Error; err != nil {
		t.Fatalf("count decision makers: %v", err)
	}
	if childCount != 0 {
		t.Fatalf("expected decision makers to be deleted, count=%d", childCount)
	}

	if err := db.Model(&domain.CustomerEquipment{}).Where("user_profile_id = ?", profile.ID).Count(&childCount).Error; err != nil {
		t.Fatalf("count equipments: %v", err)
	}
	if childCount != 0 {
		t.Fatalf("expected equipments to be deleted, count=%d", childCount)
	}

	if err := db.Model(&domain.CustomerAffiliation{}).Where("user_profile_id = ?", profile.ID).Count(&childCount).Error; err != nil {
		t.Fatalf("count affiliations: %v", err)
	}
	if childCount != 0 {
		t.Fatalf("expected affiliations to be deleted, count=%d", childCount)
	}
}

func TestListProfilesAppliesConfiguredDocumentFormat(t *testing.T) {
	db := setupCustomerProfileServiceTestDB(t)
	settingRepo := setModule.NewRepository(repository.NewRepository(db))
	settingSvc := setModule.NewService(settingRepo)
	if err := settingSvc.Set("cp_format", domain.CustomerProfileDefaultNumberFormat); err != nil {
		t.Fatalf("seed cp format setting: %v", err)
	}
	if err := settingSvc.Set("cp_reset", "global"); err != nil {
		t.Fatalf("seed cp reset setting: %v", err)
	}

	svc := newCustomerProfileTestService(db, settingSvc)

	profile := newCustomerProfileFixture("PT Format")
	if err := svc.SaveDraft(profile, 3, "admin"); err != nil {
		t.Fatalf("save draft: %v", err)
	}

	profiles, _, err := svc.ListProfiles(1, 10, "", "all", 3, "admin")
	if err != nil {
		t.Fatalf("list profiles: %v", err)
	}
	if len(profiles) != 1 {
		t.Fatalf("expected one profile, got %d", len(profiles))
	}
	expected := utils.GenerateDocRef(domain.CustomerProfileDefaultNumberFormat, 1, profile.CreatedAt)
	if profiles[0].DocumentNumber != expected {
		t.Fatalf("expected configured document number %s, got %q", expected, profiles[0].DocumentNumber)
	}
}

func TestListProfilesFiltersByTab(t *testing.T) {
	db := setupCustomerProfileServiceTestDB(t)
	svc := newCustomerProfileTestService(db, nil)

	if err := db.Create(&domain.CustomerProfile{CustomerName: "PT Wapu", BillingAddress: "A", WapuStatus: true, BranchID: 3}).Error; err != nil {
		t.Fatalf("seed wapu profile: %v", err)
	}
	if err := db.Create(&domain.CustomerProfile{CustomerName: "PT Non", BillingAddress: "B", WapuStatus: false, BranchID: 3}).Error; err != nil {
		t.Fatalf("seed non-wapu profile: %v", err)
	}

	wapuProfiles, wapuTotal, err := svc.ListProfiles(1, 10, "", "wapu", 3, "admin")
	if err != nil {
		t.Fatalf("list wapu profiles: %v", err)
	}
	if wapuTotal != 1 || len(wapuProfiles) != 1 || wapuProfiles[0].CustomerName != "PT Wapu" {
		t.Fatalf("expected single wapu profile, got total=%d profiles=%#v", wapuTotal, wapuProfiles)
	}

	nonWapuProfiles, nonWapuTotal, err := svc.ListProfiles(1, 10, "", "non_wapu", 3, "admin")
	if err != nil {
		t.Fatalf("list non-wapu profiles: %v", err)
	}
	if nonWapuTotal != 1 || len(nonWapuProfiles) != 1 || nonWapuProfiles[0].CustomerName != "PT Non" {
		t.Fatalf("expected single non-wapu profile, got total=%d profiles=%#v", nonWapuTotal, nonWapuProfiles)
	}
}

func TestListProfilesSearchRemainsScopedToBranch(t *testing.T) {
	db := setupCustomerProfileServiceTestDB(t)
	svc := newCustomerProfileTestService(db, nil)

	if err := db.Create(&domain.CustomerProfile{CustomerName: "PT Local", BillingAddress: "A", Npwp: "111", BranchID: 3}).Error; err != nil {
		t.Fatalf("seed branch-local profile: %v", err)
	}
	if err := db.Create(&domain.CustomerProfile{CustomerName: "PT Remote", BillingAddress: "B", Npwp: "999-REMOTE", BranchID: 4}).Error; err != nil {
		t.Fatalf("seed remote profile: %v", err)
	}

	profiles, total, err := svc.ListProfiles(1, 10, "999", "all", 3, "admin")
	if err != nil {
		t.Fatalf("list profiles with scoped search: %v", err)
	}
	if total != 0 || len(profiles) != 0 {
		t.Fatalf("expected no cross-branch match, got total=%d profiles=%#v", total, profiles)
	}
}

func newCustomerProfileFixture(name string) *domain.CustomerProfile {
	return &domain.CustomerProfile{
		CustomerName:     name,
		BillingAddress:   "Billing Address",
		CityBilling:      "Banjarmasin",
		Telephone:        "08123456789",
		Fax:              "021-000000",
		PurchasingEmail:  "purchasing@example.com",
		FinanceEmail:     "finance@example.com",
		WapuStatus:       true,
		DeliveryAddress:  "Delivery Address",
		CityDelivery:     "Banjarmasin",
		Npwp:             "01.234.567.8-901.000",
		ParentCompany:    "Parent Company",
		NatureOfBusiness: "Construction",
	}
}
