package dailyreport

import (
	"bytes"
	"errors"
	"testing"
	"time"

	"system-altrak/internal/domain"

	"github.com/xuri/excelize/v2"
)

type dailyReportRepoMock struct {
	listErr    error
	revertErr  error
	records    []domain.OperationalRecord
	searchArg  string
	fromArg    string
	toArg      string
	branchArg  uint
	roleArg    string
	revertID   uint
	revertBr   uint
	revertRole string
}

func (m *dailyReportRepoMock) ListHistoricalRecords(search, from, to string, branchID uint, role string) ([]domain.OperationalRecord, error) {
	m.searchArg = search
	m.fromArg = from
	m.toArg = to
	m.branchArg = branchID
	m.roleArg = role
	if m.listErr != nil {
		return nil, m.listErr
	}
	return m.records, nil
}

func (m *dailyReportRepoMock) RevertVerification(id uint, branchID uint, role string) error {
	m.revertID = id
	m.revertBr = branchID
	m.revertRole = role
	return m.revertErr
}

func (m *dailyReportRepoMock) MarkAsExported(ids []uint, branchID uint, exportedBy uint) error {
	return nil
}

func (m *dailyReportRepoMock) GetLastArchivedAt(branchID uint) (*time.Time, error) {
	return nil, nil
}

func openDailyReportWorkbook(t *testing.T, raw []byte) *excelize.File {
	t.Helper()
	f, err := excelize.OpenReader(bytes.NewReader(raw))
	if err != nil {
		t.Fatalf("failed to open workbook: %v", err)
	}
	t.Cleanup(func() { _ = f.Close() })
	return f
}

func TestFetchHistoricalRecordsDelegatesParameters(t *testing.T) {
	records := []domain.OperationalRecord{{PsoNo: "PSO-001"}}
	repo := &dailyReportRepoMock{records: records}
	svc := NewService(repo, nil)

	got, err := svc.FetchHistoricalRecords("alpha", "2026-04-01", "2026-04-30", 5, "admin")
	if err != nil {
		t.Fatalf("FetchHistoricalRecords returned error: %v", err)
	}
	if repo.searchArg != "alpha" || repo.fromArg != "2026-04-01" || repo.toArg != "2026-04-30" {
		t.Fatalf("unexpected forwarded filters: search=%q from=%q to=%q", repo.searchArg, repo.fromArg, repo.toArg)
	}
	if repo.branchArg != 5 || repo.roleArg != "admin" {
		t.Fatalf("unexpected forwarded scope: branch=%d role=%q", repo.branchArg, repo.roleArg)
	}
	if len(got) != 1 || got[0].PsoNo != "PSO-001" {
		t.Fatalf("unexpected records payload: %+v", got)
	}
}

func TestRevertVerificationDelegatesParameters(t *testing.T) {
	repo := &dailyReportRepoMock{}
	svc := NewService(repo, nil)

	if err := svc.RevertVerification(17, 3, "manager"); err != nil {
		t.Fatalf("RevertVerification returned error: %v", err)
	}
	if repo.revertID != 17 || repo.revertBr != 3 || repo.revertRole != "manager" {
		t.Fatalf("unexpected revert delegation: id=%d branch=%d role=%q", repo.revertID, repo.revertBr, repo.revertRole)
	}
}

func TestExportExcelReturnsRepositoryError(t *testing.T) {
	repo := &dailyReportRepoMock{listErr: errors.New("db down")}
	svc := NewService(repo, nil)

	_, err := svc.ExportExcel("", "", "", "", "all", nil, 1, "admin", "")
	if err == nil {
		t.Fatal("expected repository error")
	}
}

func TestExportExcelUsesFromDateInHeader(t *testing.T) {
	verifiedAt := time.Date(2026, time.April, 2, 9, 0, 0, 0, time.UTC)
	repo := &dailyReportRepoMock{
		records: []domain.OperationalRecord{
			{
				PsoNo:        "PSO-100",
				PoNo:         "PO-100",
				CustomerName: "PT Alpha",
				TOP:          30,
				Discount:     1.5,
				Currency:     "IDR",
				Amount:       12500,
				VerifiedAt:   &verifiedAt,
			},
		},
	}
	svc := NewService(repo, nil)

	raw, err := svc.ExportExcel("", "2026-04-13", "2026-04-30", "", "all", nil, 1, "admin", "")
	if err != nil {
		t.Fatalf("ExportExcel returned error: %v", err)
	}

	book := openDailyReportWorkbook(t, raw)
	perLabel, _ := book.GetCellValue("Daily Report", "A3")
	if perLabel != "PER 13 April 2026 - 30 April 2026" {
		t.Fatalf("expected header date range from from/to filters, got %q", perLabel)
	}
}

func TestExportExcelFallsBackToVerifiedAtWhenFromEmpty(t *testing.T) {
	verifiedAt := time.Date(2026, time.March, 21, 11, 0, 0, 0, time.UTC)
	repo := &dailyReportRepoMock{
		records: []domain.OperationalRecord{
			{
				PsoNo:        "PSO-200",
				PoNo:         "PO-200",
				CustomerName: "PT Beta",
				TOP:          14,
				Discount:     0,
				Currency:     "IDR",
				Amount:       10000,
				VerifiedAt:   &verifiedAt,
			},
		},
	}
	svc := NewService(repo, nil)

	raw, err := svc.ExportExcel("", "", "", "", "all", nil, 9, "manager", "")
	if err != nil {
		t.Fatalf("ExportExcel returned error: %v", err)
	}

	book := openDailyReportWorkbook(t, raw)
	perLabel, _ := book.GetCellValue("Daily Report", "A3")
	if perLabel != "PER 21 Maret 2026" {
		t.Fatalf("expected fallback header from verified_at, got %q", perLabel)
	}
	if repo.branchArg != 9 || repo.roleArg != "manager" {
		t.Fatalf("expected list scope branch=9 role=manager, got branch=%d role=%q", repo.branchArg, repo.roleArg)
	}
}

func TestExportExcelWritesRowsAndTotalInHundreds(t *testing.T) {
	psoDate := time.Date(2026, time.April, 10, 0, 0, 0, 0, time.UTC)
	repo := &dailyReportRepoMock{
		records: []domain.OperationalRecord{
			{
				PsoNo:        "PSO-301",
				PoNo:         "PO-301",
				CustomerName: "PT Gamma",
				TOP:          30,
				Discount:     2.5,
				Currency:     "IDR",
				Amount:       25000,
				PsoDate:      &psoDate,
			},
			{
				PsoNo:        "PSO-302",
				PoNo:         "PO-302",
				CustomerName: "PT Delta",
				TOP:          45,
				Discount:     1.0,
				Currency:     "USD",
				Amount:       13000,
				PsoDate:      &psoDate,
			},
		},
	}
	svc := NewService(repo, nil)

	raw, err := svc.ExportExcel("", "2026-04-10", "", "", "all", nil, 1, "admin", "")
	if err != nil {
		t.Fatalf("ExportExcel returned error: %v", err)
	}

	book := openDailyReportWorkbook(t, raw)
	if h, _ := book.GetCellValue("Daily Report", "A4"); h != "No." {
		t.Fatalf("expected table header No. at A4, got %q", h)
	}
	if psoNo, _ := book.GetCellValue("Daily Report", "B5"); psoNo != "PSO-302" {
		t.Fatalf("expected first row pso no PSO-302, got %q", psoNo)
	}
	if amountRow1, _ := book.GetCellValue("Daily Report", "I5"); amountRow1 != "130" {
		t.Fatalf("expected row1 amount 130, got %q", amountRow1)
	}
	if psoNo, _ := book.GetCellValue("Daily Report", "B6"); psoNo != "PSO-301" {
		t.Fatalf("expected second row pso no PSO-301, got %q", psoNo)
	}
	if amountRow2, _ := book.GetCellValue("Daily Report", "I6"); amountRow2 != "250" {
		t.Fatalf("expected row2 amount 250, got %q", amountRow2)
	}
	if total, _ := book.GetCellValue("Daily Report", "I7"); total != "380" {
		t.Fatalf("expected total 380, got %q", total)
	}
}

func TestExportExcelFallsBackToVerifiedAtWhenFromInvalid(t *testing.T) {
	verifiedAt := time.Date(2026, time.May, 1, 0, 0, 0, 0, time.UTC)
	repo := &dailyReportRepoMock{
		records: []domain.OperationalRecord{{
			PsoNo:      "PSO-400",
			Amount:     100,
			VerifiedAt: &verifiedAt,
		}},
	}
	svc := NewService(repo, nil)

	raw, err := svc.ExportExcel("", "13-04-2026", "", "", "all", nil, 1, "admin", "")
	if err != nil {
		t.Fatalf("ExportExcel returned error: %v", err)
	}

	book := openDailyReportWorkbook(t, raw)
	perLabel, _ := book.GetCellValue("Daily Report", "A3")
	if perLabel != "PER 1 Mei 2026" {
		t.Fatalf("expected fallback PER label from verified_at, got %q", perLabel)
	}
}

func TestExportPdfReturnsRepositoryError(t *testing.T) {
	repo := &dailyReportRepoMock{listErr: errors.New("query failed")}
	svc := NewService(repo, nil)

	_, err := svc.ExportPdf("k", "2026-04-01", "2026-04-30", "", "all", nil, 1, "admin", "")
	if err == nil {
		t.Fatal("expected repository error")
	}
}

func TestExportExcelCanIncludeAndExcludeCustomers(t *testing.T) {
	records := []domain.OperationalRecord{
		{PsoNo: "PSO-1", CustomerName: "PT Alpha", Amount: 1000, VerifiedAt: ptrTime(time.Date(2026, time.April, 1, 0, 0, 0, 0, time.UTC))},
		{PsoNo: "PSO-2", CustomerName: "PT Beta", Amount: 2000, VerifiedAt: ptrTime(time.Date(2026, time.April, 1, 0, 0, 0, 0, time.UTC))},
		{PsoNo: "PSO-3", CustomerName: "PT Gamma", Amount: 3000, VerifiedAt: ptrTime(time.Date(2026, time.April, 1, 0, 0, 0, 0, time.UTC))},
	}
	repo := &dailyReportRepoMock{records: records}
	svc := NewService(repo, nil)

	raw, err := svc.ExportExcel("", "2026-04-01", "2026-04-30", "", "include", []string{"PT Alpha", "PT Gamma"}, 1, "admin", "")
	if err != nil {
		t.Fatalf("ExportExcel returned error: %v", err)
	}
	book := openDailyReportWorkbook(t, raw)
	if psoNo, _ := book.GetCellValue("Daily Report", "B5"); psoNo != "PSO-1" {
		t.Fatalf("expected first included customer to remain, got %q", psoNo)
	}
	if psoNo, _ := book.GetCellValue("Daily Report", "B6"); psoNo != "PSO-3" {
		t.Fatalf("expected second included customer to remain, got %q", psoNo)
	}

	raw, err = svc.ExportExcel("", "2026-04-01", "2026-04-30", "", "exclude", []string{"PT Beta"}, 1, "admin", "")
	if err != nil {
		t.Fatalf("ExportExcel returned error: %v", err)
	}
	book = openDailyReportWorkbook(t, raw)
	if psoNo, _ := book.GetCellValue("Daily Report", "B5"); psoNo != "PSO-1" {
		t.Fatalf("expected first row after exclusion to be PSO-1, got %q", psoNo)
	}
	if psoNo, _ := book.GetCellValue("Daily Report", "B6"); psoNo != "PSO-3" {
		t.Fatalf("expected second row after exclusion to be PSO-3, got %q", psoNo)
	}
}

func ptrTime(value time.Time) *time.Time {
	return &value
}
