package persistence

import (
	"context"
	"fmt"
	"testing"
)

// TestIntegration_UnboundOAuthCodeDisconnectSweepOnTiDB proves the selected
// historical-code policy against TiDB. Disconnecting one app consumes the
// subject's still-redeemable unbound codes across apps, without touching a
// bound code, another subject's code, or an already-expired row.
func TestIntegration_UnboundOAuthCodeDisconnectSweepOnTiDB(t *testing.T) {
	db, cleanup := openSchemaDDLTiDB(t)
	defer cleanup()

	for _, statement := range []string{
		"CREATE TABLE `User` (Id VARCHAR(64) PRIMARY KEY)",
		"CREATE TABLE authorization_clients (Id VARCHAR(64) PRIMARY KEY)",
		`CREATE TABLE _System_OAuthCode (
			Id VARCHAR(64) PRIMARY KEY,
			ClientId VARCHAR(64) NOT NULL,
			UserId VARCHAR(64) NOT NULL,
			ConsentId VARCHAR(64) NULL,
			ExpiresAt DATETIME(6) NOT NULL,
			Used BOOLEAN NOT NULL
		)`,
		"INSERT INTO `User` (Id) VALUES ('subject-1'), ('subject-2')",
		"INSERT INTO authorization_clients (Id) VALUES ('client-a')",
		`INSERT INTO _System_OAuthCode (Id, ClientId, UserId, ConsentId, ExpiresAt, Used) VALUES
			('legacy-a', 'client-a', 'subject-1', NULL, DATE_ADD(NOW(6), INTERVAL 10 MINUTE), 0),
			('legacy-b', 'client-b', 'subject-1', '', DATE_ADD(NOW(6), INTERVAL 10 MINUTE), 0),
			('bound', 'client-a', 'subject-1', 'grant-1', DATE_ADD(NOW(6), INTERVAL 10 MINUTE), 0),
			('other-subject', 'client-b', 'subject-2', NULL, DATE_ADD(NOW(6), INTERVAL 10 MINUTE), 0),
			('expired', 'client-b', 'subject-1', NULL, DATE_SUB(NOW(6), INTERVAL 10 MINUTE), 0)`,
	} {
		if _, err := db.Exec(statement); err != nil {
			t.Fatalf("prepare unbound-code disconnect fixture: %v (%s)", err, statement)
		}
	}

	repo := NewOAuthRepository(db)
	withAuthorizationClientLockOnTiDB(t, db, "client-a", func(ctx context.Context) error {
		consumed, err := repo.ConsumeUnboundOAuthCodesForSubject(ctx, "subject-1", "client-a")
		if err != nil {
			return err
		}
		if consumed != 2 {
			return fmt.Errorf("consumed unbound codes = %d, want 2", consumed)
		}
		return nil
	})

	usedByID := map[string]bool{}
	rows, err := db.Query(`SELECT Id, Used FROM _System_OAuthCode ORDER BY Id`)
	if err != nil {
		t.Fatalf("read unbound-code sweep result: %v", err)
	}
	defer rows.Close()
	for rows.Next() {
		var id string
		var used bool
		if err := rows.Scan(&id, &used); err != nil {
			t.Fatalf("scan unbound-code sweep result: %v", err)
		}
		usedByID[id] = used
	}
	if err := rows.Err(); err != nil {
		t.Fatalf("iterate unbound-code sweep result: %v", err)
	}
	for id, wantUsed := range map[string]bool{
		"legacy-a": true, "legacy-b": true, "bound": false, "other-subject": false, "expired": false,
	} {
		if gotUsed, ok := usedByID[id]; !ok || gotUsed != wantUsed {
			t.Fatalf("code %q used = %t (present=%t), want %t", id, gotUsed, ok, wantUsed)
		}
	}

	withAuthorizationClientLockOnTiDB(t, db, "client-a", func(ctx context.Context) error {
		consumed, err := repo.ConsumeUnboundOAuthCodesForSubject(ctx, "subject-1", "client-a")
		if err != nil {
			return err
		}
		if consumed != 0 {
			return fmt.Errorf("repeat sweep consumed = %d, want 0", consumed)
		}
		return nil
	})
}
