package idem

import (
	"context"
	"errors"
	"testing"

	"github.com/tech/sendico/payments/storage"
	"github.com/tech/sendico/payments/storage/model"
	"go.mongodb.org/mongo-driver/v2/bson"
)

func TestTryReuse_NotFound(t *testing.T) {
	svc := New()
	store := &fakeStore{
		getByIdempotencyKeyFn: func(context.Context, bson.ObjectID, string) (*model.Payment, error) {
			return nil, storage.ErrPaymentNotFound
		},
	}

	payment, reused, err := svc.TryReuse(context.Background(), store, ReuseInput{
		OrganizationRef: bson.NewObjectID(),
		IdempotencyKey:  "idem-1",
		Fingerprint:     "hash-1",
	})
	if err != nil {
		t.Fatalf("TryReuse returned error: %v", err)
	}
	if reused {
		t.Fatal("expected reused=false")
	}
	if payment != nil {
		t.Fatal("expected nil payment")
	}
}

func TestTryReuse_ParamMismatch(t *testing.T) {
	svc := New()
	store := &fakeStore{
		getByIdempotencyKeyFn: func(context.Context, bson.ObjectID, string) (*model.Payment, error) {
			return &model.Payment{
				Metadata: map[string]string{
					reqHashMetaKey: "stored-hash",
				},
			}, nil
		},
	}

	_, _, err := svc.TryReuse(context.Background(), store, ReuseInput{
		OrganizationRef: bson.NewObjectID(),
		IdempotencyKey:  "idem-1",
		Fingerprint:     "new-hash",
	})
	if !errors.Is(err, ErrIdempotencyParamMismatch) {
		t.Fatalf("expected ErrIdempotencyParamMismatch, got %v", err)
	}
}

func TestTryReuse_Success(t *testing.T) {
	svc := New()
	existing := &model.Payment{
		PaymentRef: "pay-1",
		Metadata: map[string]string{
			reqHashMetaKey: "hash-1",
		},
	}
	store := &fakeStore{
		getByIdempotencyKeyFn: func(context.Context, bson.ObjectID, string) (*model.Payment, error) {
			return existing, nil
		},
	}

	payment, reused, err := svc.TryReuse(context.Background(), store, ReuseInput{
		OrganizationRef: bson.NewObjectID(),
		IdempotencyKey:  "idem-1",
		Fingerprint:     "hash-1",
	})
	if err != nil {
		t.Fatalf("TryReuse returned error: %v", err)
	}
	if !reused {
		t.Fatal("expected reused=true")
	}
	if payment != existing {
		t.Fatalf("expected existing payment, got %+v", payment)
	}
}

func TestCreateOrReuse_CreateSuccess(t *testing.T) {
	svc := New()
	store := &fakeStore{
		createFn: func(context.Context, *model.Payment) error { return nil },
	}
	newPayment := &model.Payment{
		PaymentRef: "pay-new",
	}

	got, reused, err := svc.CreateOrReuse(context.Background(), store, CreateInput{
		Payment: newPayment,
		Reuse: ReuseInput{
			OrganizationRef: bson.NewObjectID(),
			IdempotencyKey:  "idem-1",
			Fingerprint:     "hash-1",
		},
	})
	if err != nil {
		t.Fatalf("CreateOrReuse returned error: %v", err)
	}
	if reused {
		t.Fatal("expected reused=false")
	}
	if got != newPayment {
		t.Fatalf("expected created payment, got %+v", got)
	}
	if got.Metadata == nil || got.Metadata[reqHashMetaKey] != "hash-1" {
		t.Fatalf("expected payment metadata hash, got %+v", got.Metadata)
	}
}

func TestCreateOrReuse_DuplicateReturnsExisting(t *testing.T) {
	svc := New()
	existing := &model.Payment{
		PaymentRef: "pay-existing",
		Metadata: map[string]string{
			reqHashMetaKey: "hash-1",
		},
	}
	store := &fakeStore{
		createFn: func(context.Context, *model.Payment) error { return storage.ErrDuplicatePayment },
		getByIdempotencyKeyFn: func(context.Context, bson.ObjectID, string) (*model.Payment, error) {
			return existing, nil
		},
	}
	newPayment := &model.Payment{PaymentRef: "pay-new"}

	got, reused, err := svc.CreateOrReuse(context.Background(), store, CreateInput{
		Payment: newPayment,
		Reuse: ReuseInput{
			OrganizationRef: bson.NewObjectID(),
			IdempotencyKey:  "idem-1",
			Fingerprint:     "hash-1",
		},
	})
	if err != nil {
		t.Fatalf("CreateOrReuse returned error: %v", err)
	}
	if !reused {
		t.Fatal("expected reused=true")
	}
	if got != existing {
		t.Fatalf("expected existing payment, got %+v", got)
	}
}

func TestCreateOrReuse_DuplicateParamMismatch(t *testing.T) {
	svc := New()
	store := &fakeStore{
		createFn: func(context.Context, *model.Payment) error { return storage.ErrDuplicatePayment },
		getByIdempotencyKeyFn: func(context.Context, bson.ObjectID, string) (*model.Payment, error) {
			return &model.Payment{
				Metadata: map[string]string{
					reqHashMetaKey: "stored-hash",
				},
			}, nil
		},
	}

	_, _, err := svc.CreateOrReuse(context.Background(), store, CreateInput{
		Payment: &model.Payment{PaymentRef: "pay-new"},
		Reuse: ReuseInput{
			OrganizationRef: bson.NewObjectID(),
			IdempotencyKey:  "idem-1",
			Fingerprint:     "new-hash",
		},
	})
	if !errors.Is(err, ErrIdempotencyParamMismatch) {
		t.Fatalf("expected ErrIdempotencyParamMismatch, got %v", err)
	}
}

func TestCreateOrReuse_DuplicateWithoutReusableRecordReturnsDuplicate(t *testing.T) {
	svc := New()
	store := &fakeStore{
		createFn: func(context.Context, *model.Payment) error { return storage.ErrDuplicatePayment },
		getByIdempotencyKeyFn: func(context.Context, bson.ObjectID, string) (*model.Payment, error) {
			return nil, storage.ErrPaymentNotFound
		},
	}

	_, _, err := svc.CreateOrReuse(context.Background(), store, CreateInput{
		Payment: &model.Payment{PaymentRef: "pay-new"},
		Reuse: ReuseInput{
			OrganizationRef: bson.NewObjectID(),
			IdempotencyKey:  "idem-1",
			Fingerprint:     "hash-1",
		},
	})
	if !errors.Is(err, storage.ErrDuplicatePayment) {
		t.Fatalf("expected ErrDuplicatePayment, got %v", err)
	}
}
