package service

import (
	"context"
	"errors"
	"testing"

	"wucher/internal/domain/base"
)

type baseRepoMock struct {
	createBaseCalled  bool
	updateBaseCalled  bool
	deleteBaseCalled  bool
	getBaseByIDCalled bool
	listBasesCalled   bool

	lastCreateBase     *base.Base
	lastCreateCategory string
	lastUpdateBase     *base.Base
	lastUpdateCategory string
	lastDeleteBaseID   []byte
	lastDeleteCategory string
	lastGetBaseByIDID  []byte
	lastGetCategory    string
	lastListFilter     string
	lastListSort       string
	lastListLimit      int
	lastListOffset     int
	lastListCategory   string
	lastListDUL        *bool

	getBaseByIDResult *base.Base
	listBasesResult   []base.Base
	listBasesTotal    int64

	createBaseErr  error
	updateBaseErr  error
	deleteBaseErr  error
	getBaseByIDErr error
	listBasesErr   error
}

func (m *baseRepoMock) CreateBase(_ context.Context, row *base.Base, categoryType string) error {
	m.createBaseCalled = true
	m.lastCreateBase = row
	m.lastCreateCategory = categoryType
	return m.createBaseErr
}

func (m *baseRepoMock) UpdateBase(_ context.Context, row *base.Base, categoryType string) error {
	m.updateBaseCalled = true
	m.lastUpdateBase = row
	m.lastUpdateCategory = categoryType
	return m.updateBaseErr
}

func (m *baseRepoMock) DeleteBase(_ context.Context, id []byte, categoryType string) error {
	m.deleteBaseCalled = true
	m.lastDeleteBaseID = id
	m.lastDeleteCategory = categoryType
	return m.deleteBaseErr
}

func (m *baseRepoMock) GetBaseByID(_ context.Context, id []byte, categoryType string) (*base.Base, error) {
	m.getBaseByIDCalled = true
	m.lastGetBaseByIDID = id
	m.lastGetCategory = categoryType
	return m.getBaseByIDResult, m.getBaseByIDErr
}

func (m *baseRepoMock) ListBases(_ context.Context, filter, sort string, limit, offset int, categoryType string, dul *bool) ([]base.Base, int64, error) {
	m.listBasesCalled = true
	m.lastListFilter = filter
	m.lastListSort = sort
	m.lastListLimit = limit
	m.lastListOffset = offset
	m.lastListCategory = categoryType
	m.lastListDUL = dul
	return m.listBasesResult, m.listBasesTotal, m.listBasesErr
}

func TestNewBaseService(t *testing.T) {
	repo := &baseRepoMock{}
	svc := NewBaseService(repo)
	if svc == nil || svc.repo == nil {
		t.Fatalf("expected service and repo initialized")
	}
}

func TestBaseServiceCreateBase(t *testing.T) {
	repo := &baseRepoMock{}
	svc := NewBaseService(repo)
	row := &base.Base{BaseName: "Jakarta", DefaultShiftStart: "06:00:00", DefaultShiftEnd: "21:00:00"}

	if err := svc.CreateBase(context.Background(), row, "regular"); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !repo.createBaseCalled || repo.lastCreateBase != row || repo.lastCreateCategory != base.CategoryKeyRegular {
		t.Fatalf("expected CreateBase forwarded with regular category")
	}

	if err := svc.CreateBase(context.Background(), row, "hems"); err != nil {
		t.Fatalf("unexpected hems error: %v", err)
	}
	if repo.lastCreateCategory != base.CategoryKeyHEMS {
		t.Fatalf("expected hems category forwarded")
	}

	repo.createBaseErr = errors.New("create base failed")
	if err := svc.CreateBase(context.Background(), row, "regular"); err == nil {
		t.Fatalf("expected error from repo")
	}

	if err := svc.CreateBase(context.Background(), row, "invalid"); err == nil {
		t.Fatalf("expected invalid category error")
	}
}

func TestBaseServiceUpdateBase(t *testing.T) {
	repo := &baseRepoMock{}
	svc := NewBaseService(repo)
	row := &base.Base{BaseName: "Bandung", DefaultShiftStart: "06:00:00", DefaultShiftEnd: "21:00:00"}

	if err := svc.UpdateBase(context.Background(), row, "regular"); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !repo.updateBaseCalled || repo.lastUpdateBase != row || repo.lastUpdateCategory != base.CategoryKeyRegular {
		t.Fatalf("expected UpdateBase forwarded")
	}

	repo.updateBaseErr = errors.New("update base failed")
	if err := svc.UpdateBase(context.Background(), row, "regular"); err == nil {
		t.Fatalf("expected error from repo")
	}

	if err := svc.UpdateBase(context.Background(), row, "x"); err == nil {
		t.Fatalf("expected invalid category error")
	}
}

func TestBaseServiceDeleteBase(t *testing.T) {
	repo := &baseRepoMock{}
	svc := NewBaseService(repo)
	id := []byte("base-id")

	deleted, err := svc.DeleteBase(context.Background(), id, "hems")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if deleted != nil {
		t.Fatalf("expected deleted result to be nil when repo returns nil")
	}
	if !repo.deleteBaseCalled || string(repo.lastDeleteBaseID) != string(id) || repo.lastDeleteCategory != base.CategoryKeyHEMS {
		t.Fatalf("expected DeleteBase forwarded")
	}

	repo.deleteBaseErr = errors.New("delete base failed")
	if _, err := svc.DeleteBase(context.Background(), id, "hems"); err == nil {
		t.Fatalf("expected error from repo")
	}

	if _, err := svc.DeleteBase(context.Background(), id, "unknown"); err == nil {
		t.Fatalf("expected invalid category error")
	}
}

func TestBaseServiceGetBaseByID(t *testing.T) {
	expected := &base.Base{BaseName: "Surabaya"}
	repo := &baseRepoMock{getBaseByIDResult: expected}
	svc := NewBaseService(repo)
	id := []byte("base-id")

	got, err := svc.GetBaseByID(context.Background(), id, "hems")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if got != expected {
		t.Fatalf("unexpected result")
	}
	if !repo.getBaseByIDCalled || string(repo.lastGetBaseByIDID) != string(id) || repo.lastGetCategory != base.CategoryKeyHEMS {
		t.Fatalf("expected GetBaseByID forwarded")
	}

	repo.getBaseByIDErr = errors.New("get base failed")
	if _, err := svc.GetBaseByID(context.Background(), id, "regular"); err == nil {
		t.Fatalf("expected error from repo")
	}

	if _, err := svc.GetBaseByID(context.Background(), id, "invalid"); err == nil {
		t.Fatalf("expected invalid category error")
	}
}

func TestBaseServiceListBases(t *testing.T) {
	repo := &baseRepoMock{
		listBasesResult: []base.Base{{BaseName: "Bali"}},
		listBasesTotal:  1,
	}
	svc := NewBaseService(repo)

	rows, total, err := svc.ListBases(context.Background(), "find", "-base", 10, 20, "regular", nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(rows) != 1 || total != 1 {
		t.Fatalf("unexpected list result")
	}
	if !repo.listBasesCalled {
		t.Fatalf("expected ListBases forwarded to repo")
	}
	if repo.lastListFilter != "find" || repo.lastListSort != "base DESC" || repo.lastListLimit != 10 || repo.lastListOffset != 20 || repo.lastListCategory != base.CategoryKeyRegular {
		t.Fatalf("unexpected list args forwarded")
	}

	rows, total, err = svc.ListBases(context.Background(), "find", "sortkey", 10, 20, "regular", nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(rows) != 1 || total != 1 {
		t.Fatalf("unexpected list result")
	}
	if repo.lastListSort != "is_active DESC, CASE WHEN is_active = 1 AND sortkey IS NOT NULL AND sortkey >= 0 THEN 0 ELSE 1 END ASC, CASE WHEN is_active = 1 AND sortkey IS NOT NULL AND sortkey >= 0 THEN sortkey END ASC, base ASC" {
		t.Fatalf("unexpected sortkey args forwarded: %q", repo.lastListSort)
	}

	repo.listBasesErr = errors.New("list bases failed")
	if _, _, err := svc.ListBases(context.Background(), "", "unknown", 1, 0, "hems", nil); err == nil {
		t.Fatalf("expected error from repo")
	}
	if repo.lastListSort != "" {
		t.Fatalf("expected unknown sort normalized to empty")
	}
	if repo.lastListCategory != base.CategoryKeyHEMS {
		t.Fatalf("expected hems category forwarded")
	}

	if _, _, err := svc.ListBases(context.Background(), "", "base", 1, 0, "invalid", nil); err == nil {
		t.Fatalf("expected invalid category error")
	}

	repo.listBasesErr = nil
	if _, _, err := svc.ListBases(context.Background(), "", "base", 1, 0, "", nil); err != nil {
		t.Fatalf("expected empty category to list all, got err=%v", err)
	}
	if repo.lastListCategory != "" {
		t.Fatalf("expected empty category forwarded for unfiltered list, got %q", repo.lastListCategory)
	}
}

func TestNormalizeBaseSort(t *testing.T) {
	cases := map[string]string{
		"sortkey":            "is_active DESC, CASE WHEN is_active = 1 AND sortkey IS NOT NULL AND sortkey >= 0 THEN 0 ELSE 1 END ASC, CASE WHEN is_active = 1 AND sortkey IS NOT NULL AND sortkey >= 0 THEN sortkey END ASC, base ASC",
		"-sortkey":           "is_active DESC, CASE WHEN is_active = 1 AND sortkey IS NOT NULL AND sortkey >= 0 THEN 0 ELSE 1 END ASC, CASE WHEN is_active = 1 AND sortkey IS NOT NULL AND sortkey >= 0 THEN sortkey END DESC, base ASC",
		"base":               "base ASC",
		"-base":              "base DESC",
		"base_abbreviation":  "base_abbreviation ASC",
		"-base_abbreviation": "base_abbreviation DESC",
		"note":               "notes ASC",
		"-note":              "notes DESC",
		"notes":              "notes ASC",
		"-notes":             "notes DESC",
		"is_active":          "is_active ASC",
		"-is_active":         "is_active DESC",
		"created_at":         "created_at ASC",
		"-created_at":        "created_at DESC",
		"updated_at":         "updated_at ASC",
		"-updated_at":        "updated_at DESC",
		"unknown":            "",
		"":                   "",
	}

	for in, want := range cases {
		if got := normalizeBaseSort(in); got != want {
			t.Fatalf("normalizeBaseSort(%q) = %q, want %q", in, got, want)
		}
	}
}
