package service

import (
	"context"
	"errors"
	"testing"

	"wucher/internal/domain/helicopter"
)

type helicopterRepoMock struct {
	createCalled           bool
	updateCalled           bool
	deleteCalled           bool
	getByIDCalled          bool
	listCalled             bool
	nextReportNumberCalled bool
	isInUseCalled          bool
	inUseByIDsCalled       bool
	existsByIdentifier     bool
	existsCalled           bool

	lastCreate       *helicopter.Helicopter
	lastUpdate       *helicopter.Helicopter
	lastDeleteID     []byte
	lastGetByID      []byte
	lastListFilter   string
	lastListStatuses []string
	lastListSort     string
	lastListLimit    int
	lastListOffset   int
	lastNextReportID []byte
	lastIsInUseID    []byte
	lastInUseIDs     [][]byte

	getByIDResult          *helicopter.Helicopter
	listResult             []helicopter.Helicopter
	listTotal              int64
	nextReportNumberResult string
	nextReportCounter      int
	isInUseResult          bool
	inUseByIDsResult       map[string]bool
	existsByIdentifierID   string
	existsByIdentifierSkip []byte

	createErr           error
	updateErr           error
	deleteErr           error
	getByIDErr          error
	listErr             error
	nextReportNumberErr error
	isInUseErr          error
	inUseByIDsErr       error
	existsErr           error
}

func (m *helicopterRepoMock) Create(_ context.Context, row *helicopter.Helicopter) error {
	m.createCalled = true
	m.lastCreate = row
	return m.createErr
}

func (m *helicopterRepoMock) Update(_ context.Context, row *helicopter.Helicopter) error {
	m.updateCalled = true
	m.lastUpdate = row
	return m.updateErr
}

func (m *helicopterRepoMock) Delete(_ context.Context, id []byte) error {
	m.deleteCalled = true
	m.lastDeleteID = id
	return m.deleteErr
}

func (m *helicopterRepoMock) GetByID(_ context.Context, id []byte) (*helicopter.Helicopter, error) {
	m.getByIDCalled = true
	m.lastGetByID = id
	return m.getByIDResult, m.getByIDErr
}

func (m *helicopterRepoMock) List(_ context.Context, filter string, statuses []string, sort string, limit, offset int, _ [][]byte) ([]helicopter.Helicopter, int64, error) {
	m.listCalled = true
	m.lastListFilter = filter
	m.lastListStatuses = statuses
	m.lastListSort = sort
	m.lastListLimit = limit
	m.lastListOffset = offset
	return m.listResult, m.listTotal, m.listErr
}

func (m *helicopterRepoMock) NextReportNumber(_ context.Context, id []byte) (string, int, error) {
	m.nextReportNumberCalled = true
	m.lastNextReportID = id
	return m.nextReportNumberResult, m.nextReportCounter, m.nextReportNumberErr
}

func (m *helicopterRepoMock) IsInUse(_ context.Context, id []byte) (bool, error) {
	m.isInUseCalled = true
	m.lastIsInUseID = append([]byte(nil), id...)
	return m.isInUseResult, m.isInUseErr
}

func (m *helicopterRepoMock) InUseByAircraftIDs(_ context.Context, ids [][]byte) (map[string]bool, error) {
	m.inUseByIDsCalled = true
	m.lastInUseIDs = m.lastInUseIDs[:0]
	for i := range ids {
		m.lastInUseIDs = append(m.lastInUseIDs, append([]byte(nil), ids[i]...))
	}
	if m.inUseByIDsResult == nil {
		return map[string]bool{}, m.inUseByIDsErr
	}
	return m.inUseByIDsResult, m.inUseByIDsErr
}

func (m *helicopterRepoMock) ActiveFlightIDByAircraftIDs(_ context.Context, _ [][]byte) (map[string][]byte, error) {
	return map[string][]byte{}, nil
}

func (m *helicopterRepoMock) ExistsByIdentifier(_ context.Context, identifier string, excludeID []byte) (bool, error) {
	m.existsCalled = true
	m.existsByIdentifierID = identifier
	m.existsByIdentifierSkip = append([]byte(nil), excludeID...)
	return m.existsByIdentifier, m.existsErr
}

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

func TestHelicopterServiceCreate(t *testing.T) {
	repo := &helicopterRepoMock{}
	svc := NewHelicopterService(repo)
	row := &helicopter.Helicopter{Designation: "H145", Identifier: "PK-ABC", Type: "Twin"}

	if err := svc.Create(context.Background(), row); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !repo.createCalled || repo.lastCreate != row {
		t.Fatalf("expected Create forwarded to repo")
	}

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

func TestHelicopterServiceUpdate(t *testing.T) {
	repo := &helicopterRepoMock{}
	svc := NewHelicopterService(repo)
	row := &helicopter.Helicopter{Designation: "H145", Identifier: "PK-ABC", Type: "Twin"}

	if err := svc.Update(context.Background(), row); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !repo.updateCalled || repo.lastUpdate != row {
		t.Fatalf("expected Update forwarded to repo")
	}

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

func TestHelicopterServiceDelete(t *testing.T) {
	repo := &helicopterRepoMock{}
	svc := NewHelicopterService(repo)
	id := []byte("id")

	if err := svc.Delete(context.Background(), id); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !repo.deleteCalled {
		t.Fatalf("expected Delete forwarded to repo")
	}
	if string(repo.lastDeleteID) != string(id) {
		t.Fatalf("expected delete id forwarded")
	}

	repo.deleteErr = errors.New("delete failed")
	if err := svc.Delete(context.Background(), id); err == nil {
		t.Fatalf("expected error from repo")
	}
}

func TestHelicopterServiceGetByID(t *testing.T) {
	expected := &helicopter.Helicopter{Designation: "H145", Identifier: "PK-ABC", Type: "Twin"}
	repo := &helicopterRepoMock{getByIDResult: expected}
	svc := NewHelicopterService(repo)
	id := []byte("id")

	got, err := svc.GetByID(context.Background(), id)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if got != expected {
		t.Fatalf("unexpected result")
	}
	if !repo.getByIDCalled || string(repo.lastGetByID) != string(id) {
		t.Fatalf("expected GetByID forwarded")
	}

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

func TestHelicopterServiceList(t *testing.T) {
	repo := &helicopterRepoMock{
		listResult: []helicopter.Helicopter{{Designation: "H145", Identifier: "PK-ABC", Type: "Twin"}},
		listTotal:  1,
	}
	svc := NewHelicopterService(repo)

	rows, total, err := svc.List(context.Background(), "find", []string{"available", "booked"}, "-designation", 10, 20, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(rows) != 1 || total != 1 {
		t.Fatalf("unexpected list result")
	}
	if !repo.listCalled {
		t.Fatalf("expected List forwarded to repo")
	}
	if repo.lastListFilter != "find" || repo.lastListSort != "designation DESC" || repo.lastListLimit != 10 || repo.lastListOffset != 20 {
		t.Fatalf("unexpected list args forwarded")
	}
	if len(repo.lastListStatuses) != 2 || repo.lastListStatuses[0] != "available" || repo.lastListStatuses[1] != "booked" {
		t.Fatalf("unexpected status filter forwarded: %v", repo.lastListStatuses)
	}

	repo.listErr = errors.New("list failed")
	if _, _, err := svc.List(context.Background(), "", nil, "unknown", 1, 0, nil); err == nil {
		t.Fatalf("expected error from repo")
	}
	if repo.lastListSort != "" {
		t.Fatalf("expected unknown sort normalized to empty")
	}
}

func TestHelicopterServiceExistsByIdentifier(t *testing.T) {
	repo := &helicopterRepoMock{existsByIdentifier: true}
	svc := NewHelicopterService(repo)
	excludeID := []byte("id")

	exists, err := svc.ExistsByIdentifier(context.Background(), "SN-0001", excludeID)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !exists {
		t.Fatalf("expected duplicate exists result")
	}
	if !repo.existsCalled || repo.existsByIdentifierID != "SN-0001" || string(repo.existsByIdentifierSkip) != string(excludeID) {
		t.Fatalf("expected ExistsByIdentifier forwarded")
	}

	repo.existsErr = errors.New("exists failed")
	if _, err := svc.ExistsByIdentifier(context.Background(), "SN-0001", nil); err == nil {
		t.Fatalf("expected error from repo")
	}
}

func TestHelicopterServiceNextReportNumber(t *testing.T) {
	repo := &helicopterRepoMock{
		nextReportNumberResult: "RPT-0001",
		nextReportCounter:      1,
	}
	svc := NewHelicopterService(repo)
	id := []byte("id")

	number, counter, err := svc.NextReportNumber(context.Background(), id)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if number != "RPT-0001" || counter != 1 {
		t.Fatalf("unexpected next report result")
	}
	if !repo.nextReportNumberCalled || string(repo.lastNextReportID) != string(id) {
		t.Fatalf("expected NextReportNumber forwarded")
	}

	repo.nextReportNumberErr = errors.New("next failed")
	if _, _, err := svc.NextReportNumber(context.Background(), id); err == nil {
		t.Fatalf("expected error from repo")
	}
}

func TestNormalizeHelicopterSort(t *testing.T) {
	cases := map[string]string{
		"designation":  "designation ASC",
		"-designation": "designation DESC",
		"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, designation 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, designation ASC",
		"identifier":   "",
		"-identifier":  "",
		"type":         "",
		"-type":        "",
		"counter":      "",
		"-counter":     "",
		"created_at":   "",
		"-created_at":  "",
		"updated_at":   "",
		"-updated_at":  "",
		"unknown":      "",
		"":             "",
	}

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