//go:build integration
// +build integration

package orchestrator

import (
	"context"
	"strings"
	"testing"
	"time"

	"github.com/tech/sendico/payments/orchestrator/storage/model"
	storagemongo "github.com/tech/sendico/payments/orchestrator/storage/mongo"
	"github.com/tech/sendico/pkg/db/repository"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
	orchestratorv1 "github.com/tech/sendico/pkg/proto/payments/orchestrator/v1"
	"github.com/testcontainers/testcontainers-go"
	"github.com/testcontainers/testcontainers-go/modules/mongodb"
	"github.com/testcontainers/testcontainers-go/wait"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.mongodb.org/mongo-driver/v2/mongo"
	"go.mongodb.org/mongo-driver/v2/mongo/options"
	"go.mongodb.org/mongo-driver/v2/mongo/readpref"
	"go.uber.org/zap"
)

func terminateMongo(ctx context.Context, t *testing.T, container *mongodb.MongoDBContainer) {
	t.Helper()
	if err := container.Terminate(ctx); err != nil {
		t.Fatalf("failed to terminate MongoDB container: %v", err)
	}
}

func disconnectMongo(ctx context.Context, t *testing.T, client *mongo.Client) {
	t.Helper()
	if err := client.Disconnect(ctx); err != nil {
		t.Fatalf("failed to disconnect from MongoDB: %v", err)
	}
}

func TestQuotePayment_IdempotencyReuseAfterExpiry(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
	defer cancel()

	mongoContainer, err := mongodb.Run(ctx,
		"mongo:latest",
		mongodb.WithUsername("root"),
		mongodb.WithPassword("password"),
		testcontainers.WithWaitStrategy(wait.ForLog("Waiting for connections")),
	)
	if err != nil {
		t.Fatalf("failed to start MongoDB container: %v", err)
	}
	defer terminateMongo(ctx, t, mongoContainer)

	mongoURI, err := mongoContainer.ConnectionString(ctx)
	if err != nil {
		t.Fatalf("failed to get MongoDB connection string: %v", err)
	}

	client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
	if err != nil {
		t.Fatalf("failed to connect to MongoDB: %v", err)
	}
	defer disconnectMongo(ctx, t, client)

	db := client.Database("test_" + strings.ReplaceAll(t.Name(), "/", "_"))
	paymentsRepo := repository.CreateMongoRepository(db, (&model.Payment{}).Collection())
	quotesRepo := repository.CreateMongoRepository(db, (&model.PaymentQuoteRecord{}).Collection())
	routesRepo := repository.CreateMongoRepository(db, (&model.PaymentRoute{}).Collection())
	plansRepo := repository.CreateMongoRepository(db, (&model.PaymentPlanTemplate{}).Collection())

	ping := func(ctx context.Context) error { return client.Ping(ctx, readpref.Primary()) }
	store, err := storagemongo.NewWithRepository(zap.NewNop(), ping, paymentsRepo, quotesRepo, routesRepo, plansRepo)
	if err != nil {
		t.Fatalf("failed to create payments repository: %v", err)
	}

	now := time.Now()
	svc := NewService(zap.NewNop(), store, WithClock(testClock{now: now}))

	orgID := bson.NewObjectID()
	req := &orchestratorv1.QuotePaymentRequest{
		Meta:           &orchestratorv1.RequestMeta{OrganizationRef: orgID.Hex()},
		IdempotencyKey: "idem-expired-quote",
		Ref:            "ref-expired",
		Intent: &orchestratorv1.PaymentIntent{
			Ref: "intent-1",
			Source: &orchestratorv1.PaymentEndpoint{
				Endpoint: &orchestratorv1.PaymentEndpoint_Ledger{
					Ledger: &orchestratorv1.LedgerEndpoint{LedgerAccountRef: "ledger:source"},
				},
			},
			Destination: &orchestratorv1.PaymentEndpoint{
				Endpoint: &orchestratorv1.PaymentEndpoint_Ledger{
					Ledger: &orchestratorv1.LedgerEndpoint{LedgerAccountRef: "ledger:dest"},
				},
			},
			Amount:             &moneyv1.Money{Currency: "USDT", Amount: "1"},
			SettlementCurrency: "USDT",
		},
	}

	resp1, err := svc.QuotePayment(ctx, req)
	if err != nil {
		t.Fatalf("first quote returned error: %v", err)
	}
	firstRef := resp1.GetQuote().GetQuoteRef()
	if firstRef == "" {
		t.Fatal("expected first quote ref to be populated")
	}

	quotesColl := db.Collection((&model.PaymentQuoteRecord{}).Collection())
	update := bson.M{"$set": bson.M{"expiresAt": time.Now().Add(-time.Minute)}}
	result, err := quotesColl.UpdateOne(ctx, bson.M{
		"organizationRef": orgID,
		"idempotencyKey":  req.GetIdempotencyKey(),
	}, update)
	if err != nil {
		t.Fatalf("failed to expire quote: %v", err)
	}
	if result.MatchedCount == 0 {
		t.Fatal("expected expired quote to be updated")
	}

	resp2, err := svc.QuotePayment(ctx, req)
	if err != nil {
		t.Fatalf("second quote returned error: %v", err)
	}
	secondRef := resp2.GetQuote().GetQuoteRef()
	if secondRef == "" {
		t.Fatal("expected second quote ref to be populated")
	}
	if secondRef == firstRef {
		t.Fatal("expected a new quote to be generated after expiry")
	}
}
