package service

import (
	"context"
	"errors"
	"testing"
	"time"

	easarelease "wucher/internal/domain/easa_release"
)

type stubEASARepo struct {
	row       *easarelease.EASARelease
	updated   *easarelease.EASARelease
	created   *easarelease.EASARelease
	voidedID  []byte
	createErr error
	getErr    error
	voidErr   error
}

func (s *stubEASARepo) Create(_ context.Context, row *easarelease.EASARelease) error {
	s.created = row
	if len(row.ID) == 0 {
		row.ID = []byte("newid0123456789a")
	}
	return s.createErr
}
func (s *stubEASARepo) Update(_ context.Context, row *easarelease.EASARelease) error {
	s.updated = row
	return nil
}
func (s *stubEASARepo) Delete(_ context.Context, _, _ []byte) error { return nil }
func (s *stubEASARepo) GetByID(_ context.Context, _ []byte) (*easarelease.EASARelease, error) {
	return s.row, s.getErr
}
func (s *stubEASARepo) GetByFlightID(_ context.Context, _ []byte) (*easarelease.EASARelease, error) {
	return s.row, s.getErr
}
func (s *stubEASARepo) GetByComplaintID(_ context.Context, _ []byte) (*easarelease.EASARelease, error) {
	return s.row, s.getErr
}
func (s *stubEASARepo) ListByHelicopter(_ context.Context, _ []byte) ([]easarelease.EASARelease, error) {
	if s.row != nil {
		return []easarelease.EASARelease{*s.row}, nil
	}
	return nil, nil
}
func (s *stubEASARepo) GetLatestByHelicopter(_ context.Context, _ []byte) (*easarelease.EASARelease, error) {
	return s.row, nil
}
func (s *stubEASARepo) GetActiveByHelicopter(_ context.Context, _ []byte) (*easarelease.EASARelease, error) {
	return s.row, nil
}
func (s *stubEASARepo) VoidByID(_ context.Context, id []byte, _ string, _ []byte) error {
	s.voidedID = id
	return s.voidErr
}

func completeUnsigned() *easarelease.EASARelease {
	now := time.Now()
	return &easarelease.EASARelease{
		ID:           []byte("0123456789abcdef"),
		HelicopterID: []byte("fedcba9876543210"),
		EASADate:     &now,
		EASAACHours:  ptr("1245.3"),
		EASALocation: ptr("LOWI"),
		EASAWONo:     ptr("WO-1"),
		EASASignerID: ptr("019d61db-53c6-7280-87ec-e65f205c6d3b"),
	}
}

func TestEASAReleaseServiceSign(t *testing.T) {
	actor := []byte("aaaaaaaaaaaaaaaa")
	ctx := context.Background()

	if err := NewEASAReleaseService(&stubEASARepo{row: nil}).Sign(ctx, []byte("x"), actor); !errors.Is(err, easarelease.ErrNotFound) {
		t.Fatalf("want ErrNotFound, got %v", err)
	}

	incompleteRepo := &stubEASARepo{row: &easarelease.EASARelease{ID: []byte("0123456789abcdef")}}
	if err := NewEASAReleaseService(incompleteRepo).Sign(ctx, incompleteRepo.row.ID, actor); !errors.Is(err, easarelease.ErrIncompleteForSign) {
		t.Fatalf("want ErrIncompleteForSign, got %v", err)
	}
	if incompleteRepo.updated != nil {
		t.Fatal("incomplete release must not be persisted signed")
	}

	signed := completeUnsigned()
	at := time.Now()
	signed.SignedAt = &at
	if err := NewEASAReleaseService(&stubEASARepo{row: signed}).Sign(ctx, signed.ID, actor); !errors.Is(err, easarelease.ErrAlreadySigned) {
		t.Fatalf("want ErrAlreadySigned, got %v", err)
	}

	okRepo := &stubEASARepo{row: completeUnsigned()}
	if err := NewEASAReleaseService(okRepo).Sign(ctx, okRepo.row.ID, actor); err != nil {
		t.Fatalf("unexpected err: %v", err)
	}
	if okRepo.updated == nil || okRepo.updated.SignedAt == nil || string(okRepo.updated.SignedBy) != string(actor) {
		t.Fatal("complete release should be persisted signed by actor")
	}
}

func TestEASAReleaseServiceAmendAndDelegation(t *testing.T) {
	ctx := context.Background()
	repo := &stubEASARepo{row: completeUnsigned()}
	svc := NewEASAReleaseService(repo)

	newID, err := svc.Amend(ctx, repo.row.ID, "superseded", []byte("actor"))
	if err != nil {
		t.Fatalf("Amend err: %v", err)
	}
	if string(repo.voidedID) != string(repo.row.ID) {
		t.Fatal("Amend should void the current release")
	}
	if repo.created == nil || string(repo.created.HelicopterID) != string(repo.row.HelicopterID) {
		t.Fatal("Amend should create a copy for the same helicopter")
	}
	if len(newID) == 0 {
		t.Fatal("Amend should return the new id")
	}

	if _, err := svc.GetLatestByHelicopter(ctx, []byte("h")); err != nil {
		t.Fatalf("GetLatestByHelicopter: %v", err)
	}
	if _, err := svc.GetActiveByHelicopter(ctx, []byte("h")); err != nil {
		t.Fatalf("GetActiveByHelicopter: %v", err)
	}
	if rows, err := svc.ListByHelicopter(ctx, []byte("h")); err != nil || len(rows) != 1 {
		t.Fatalf("ListByHelicopter: %v", err)
	}
	if err := svc.Delete(ctx, []byte("x"), nil); err != nil {
		t.Fatalf("Delete: %v", err)
	}
	if _, err := svc.GetByID(ctx, []byte("x")); err != nil {
		t.Fatalf("GetByID: %v", err)
	}

	// Amend on missing release.
	if _, err := NewEASAReleaseService(&stubEASARepo{row: nil}).Amend(ctx, []byte("x"), "r", nil); !errors.Is(err, easarelease.ErrNotFound) {
		t.Fatalf("Amend missing: want ErrNotFound, got %v", err)
	}
	// Amend with void error.
	if _, err := NewEASAReleaseService(&stubEASARepo{row: completeUnsigned(), voidErr: errors.New("boom")}).Amend(ctx, []byte("x"), "r", nil); err == nil {
		t.Fatal("Amend should propagate void error")
	}
	// Amend with create error.
	if _, err := NewEASAReleaseService(&stubEASARepo{row: completeUnsigned(), createErr: errors.New("boom")}).Amend(ctx, []byte("x"), "r", nil); err == nil {
		t.Fatal("Amend should propagate create error")
	}
	// Amend with GetByID error.
	if _, err := NewEASAReleaseService(&stubEASARepo{getErr: errors.New("boom")}).Amend(ctx, []byte("x"), "r", nil); err == nil {
		t.Fatal("Amend should propagate GetByID error")
	}
}

func TestEASAReleaseServiceCreateUpdateAndErrors(t *testing.T) {
	ctx := context.Background()
	repo := &stubEASARepo{}
	svc := NewEASAReleaseService(repo)
	row := completeUnsigned()
	if err := svc.Create(ctx, row); err != nil || repo.created != row {
		t.Fatalf("Create not delegated: %v", err)
	}
	if err := svc.Update(ctx, row); err != nil || repo.updated != row {
		t.Fatalf("Update not delegated: %v", err)
	}
	// Sign with GetByID error.
	if err := NewEASAReleaseService(&stubEASARepo{getErr: errors.New("boom")}).Sign(ctx, []byte("x"), nil); err == nil {
		t.Fatal("Sign should propagate GetByID error")
	}
}
