package iom

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

	"system-altrak/internal/domain"

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

type iomRepoMock struct {
	createErr error
	createArg *domain.Memorandum
	sequence  int
	seqErr    error

	getErr    error
	getArgID  uint
	getArgBID uint
	getArgRol string
	getMemo   *domain.Memorandum

	listErr    error
	listArgBID uint
	listArgRol string
	listMemos  []domain.Memorandum

	deleteErr    error
	deleteArgID  uint
	deleteArgBID uint
	deleteArgRol string
}

func (m *iomRepoMock) CreateMemorandum(memo *domain.Memorandum) error {
	m.createArg = memo
	return m.createErr
}

func (m *iomRepoMock) GetNextSequence(docType string, year, month int) (int, error) {
	if m.seqErr != nil {
		return 0, m.seqErr
	}
	if m.sequence == 0 {
		return 1, nil
	}
	return m.sequence, nil
}

func (m *iomRepoMock) GetMemorandumByID(id uint, branchID uint, role string) (*domain.Memorandum, error) {
	m.getArgID = id
	m.getArgBID = branchID
	m.getArgRol = role
	if m.getErr != nil {
		return nil, m.getErr
	}
	if m.getMemo != nil {
		copy := *m.getMemo
		return &copy, nil
	}
	return &domain.Memorandum{Ref: "IOM-DEFAULT"}, nil
}

func (m *iomRepoMock) ListMemorandums(branchID uint, role string) ([]domain.Memorandum, error) {
	m.listArgBID = branchID
	m.listArgRol = role
	if m.listErr != nil {
		return nil, m.listErr
	}
	return m.listMemos, nil
}

func (m *iomRepoMock) DeleteMemorandum(id uint, branchID uint, role string) error {
	m.deleteArgID = id
	m.deleteArgBID = branchID
	m.deleteArgRol = role
	return m.deleteErr
}

func TestCreateMemorandumSetsTimestamps(t *testing.T) {
	repo := &iomRepoMock{sequence: 7}
	svc := NewService(repo, nil)
	memo := &domain.Memorandum{Subject: "Memo"}

	before := time.Now().Add(-2 * time.Second)
	if err := svc.CreateMemorandum(memo); err != nil {
		t.Fatalf("CreateMemorandum returned error: %v", err)
	}

	if repo.createArg != memo {
		t.Fatal("expected repository to receive same memorandum pointer")
	}
	if memo.CreatedAt.IsZero() || memo.Date.IsZero() {
		t.Fatal("expected CreatedAt and Date to be initialized")
	}
	if memo.CreatedAt.Before(before) {
		t.Fatalf("expected CreatedAt to be current timestamp, got: %v", memo.CreatedAt)
	}
	if !memo.CreatedAt.Equal(memo.Date) {
		t.Fatalf("expected CreatedAt and Date to match, got created_at=%v date=%v", memo.CreatedAt, memo.Date)
	}
	if memo.Ref == "" || !strings.Contains(memo.Ref, "IOM-BJM") {
		t.Fatalf("expected generated ref to contain IOM-BJM, got %q", memo.Ref)
	}
}

func TestGetMemorandumByIDDelegatesToRepository(t *testing.T) {
	expected := &domain.Memorandum{Ref: "IOM-001", Subject: "Delegation"}
	repo := &iomRepoMock{getMemo: expected}
	svc := NewService(repo, nil)

	memo, err := svc.GetMemorandumByID(9, 3, "admin")
	if err != nil {
		t.Fatalf("GetMemorandumByID returned error: %v", err)
	}
	if repo.getArgID != 9 || repo.getArgBID != 3 || repo.getArgRol != "admin" {
		t.Fatalf("unexpected delegation args: id=%d branch=%d role=%q", repo.getArgID, repo.getArgBID, repo.getArgRol)
	}
	if memo == nil || memo.Ref != "IOM-001" {
		t.Fatalf("unexpected memorandum payload: %+v", memo)
	}
}

func TestListMemorandumsDelegatesToRepository(t *testing.T) {
	repo := &iomRepoMock{listMemos: []domain.Memorandum{{Ref: "IOM-100"}}}
	svc := NewService(repo, nil)

	memos, err := svc.ListMemorandums(8, "manager")
	if err != nil {
		t.Fatalf("ListMemorandums returned error: %v", err)
	}
	if repo.listArgBID != 8 || repo.listArgRol != "manager" {
		t.Fatalf("unexpected delegation args: branch=%d role=%q", repo.listArgBID, repo.listArgRol)
	}
	if len(memos) != 1 || memos[0].Ref != "IOM-100" {
		t.Fatalf("unexpected memorandums payload: %+v", memos)
	}
}

func TestDeleteMemorandumDelegatesToRepository(t *testing.T) {
	repo := &iomRepoMock{}
	svc := NewService(repo, nil)

	if err := svc.DeleteMemorandum(21, 5, "superadmin"); err != nil {
		t.Fatalf("DeleteMemorandum returned error: %v", err)
	}
	if repo.deleteArgID != 21 || repo.deleteArgBID != 5 || repo.deleteArgRol != "superadmin" {
		t.Fatalf("unexpected delegation args: id=%d branch=%d role=%q", repo.deleteArgID, repo.deleteArgBID, repo.deleteArgRol)
	}
}

func TestExportMemorandumsExcelBuildsWorkbook(t *testing.T) {
	d := time.Date(2026, time.April, 14, 10, 30, 0, 0, time.UTC)
	repo := &iomRepoMock{listMemos: []domain.Memorandum{{
		Ref:            "IOM-2026-001",
		Date:           d,
		ToDepartment:   "Service",
		FromDepartment: "Parts",
		Subject:        "Routine Update",
		SignedBy:       "Manager",
		Status:         "submitted",
	}}}
	svc := NewService(repo, nil)

	buf, err := svc.ExportMemorandumsExcel(2, "admin")
	if err != nil {
		t.Fatalf("ExportMemorandumsExcel returned error: %v", err)
	}
	if repo.listArgBID != 2 || repo.listArgRol != "admin" {
		t.Fatalf("unexpected list delegation args: branch=%d role=%q", repo.listArgBID, repo.listArgRol)
	}

	xlsx, err := excelize.OpenReader(bytes.NewReader(buf))
	if err != nil {
		t.Fatalf("failed to parse generated xlsx: %v", err)
	}
	defer xlsx.Close()

	if got := xlsx.GetSheetName(0); got != "IOM" {
		t.Fatalf("expected first sheet name IOM, got %q", got)
	}
	if got, _ := xlsx.GetCellValue("IOM", "A1"); got != "No" {
		t.Fatalf("expected A1 header No, got %q", got)
	}
	if got, _ := xlsx.GetCellValue("IOM", "C2"); got != "IOM-2026-001" {
		t.Fatalf("expected C2 ref value, got %q", got)
	}
	if got, _ := xlsx.GetCellValue("IOM", "H2"); got != "submitted" {
		t.Fatalf("expected H2 status value, got %q", got)
	}
}

func TestExportMemorandumsExcelPropagatesRepositoryError(t *testing.T) {
	repoErr := errors.New("repository unavailable")
	repo := &iomRepoMock{listErr: repoErr}
	svc := NewService(repo, nil)

	buf, err := svc.ExportMemorandumsExcel(1, "admin")
	if !errors.Is(err, repoErr) {
		t.Fatalf("expected repository error, got: %v", err)
	}
	if buf != nil {
		t.Fatal("expected nil buffer when repository returns error")
	}
}

func TestSynthesizePdfPropagatesRepositoryError(t *testing.T) {
	repoErr := errors.New("record lookup failed")
	repo := &iomRepoMock{getErr: repoErr}
	svc := NewService(repo, nil)

	pdf, name, err := svc.SynthesizePdf(11, 4, "admin")
	if !errors.Is(err, repoErr) {
		t.Fatalf("expected repository error, got: %v", err)
	}
	if pdf != nil {
		t.Fatal("expected nil pdf bytes when repository returns error")
	}
	if name != "" {
		t.Fatalf("expected empty name on error, got %q", name)
	}
}
