package persistence

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/shared/pkg/models"
)

// TestIntegration_AuthorizationGrantBoundRefreshRotationOnTiDB proves the
// credential-side composition against a real TiDB: client lock -> subject and
// grant lock -> refresh-family rotation all use one pessimistic transaction.
// It also proves that the exact era read on that transaction becomes inactive
// after disconnect. A scripted SQL stub cannot establish either property
// because it has no real row locks or transaction visibility.
func TestIntegration_AuthorizationGrantBoundRefreshRotationOnTiDB(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_OAuthConsent (
			Id VARCHAR(64) PRIMARY KEY,
			UserId VARCHAR(64) NOT NULL,
			ConnectedAppId VARCHAR(64) NOT NULL,
			Scope TEXT NOT NULL,
			ConsentVersion INT NOT NULL,
			ApprovedAt DATETIME(6) NOT NULL,
			RevokedAt DATETIME(6) NULL,
			RevokedBy VARCHAR(255) NULL,
			CreatedDate DATETIME NOT NULL,
			LastModifiedDate DATETIME NOT NULL,
			INDEX grant_lookup (UserId, ConnectedAppId, ConsentVersion)
		)`,
		`CREATE TABLE _System_OAuthRefreshToken (
			Id VARCHAR(64) PRIMARY KEY,
			TokenHash VARCHAR(128) NOT NULL UNIQUE,
			UserId VARCHAR(64) NOT NULL,
			ConnectedAppId VARCHAR(64) NULL,
			FamilyId VARCHAR(64) NULL,
			ConsentId VARCHAR(64) NULL,
			Scope TEXT NULL,
			ExpiresAt DATETIME NOT NULL,
			IsRevoked BOOLEAN NOT NULL,
			CreatedDate DATETIME NOT NULL,
			LastModifiedDate DATETIME NOT NULL,
			INDEX refresh_family (FamilyId)
		)`,
		"INSERT INTO `User` (Id) VALUES ('subject-1')",
		"INSERT INTO authorization_clients (Id) VALUES ('client-1')",
	} {
		if _, err := db.Exec(statement); err != nil {
			t.Fatalf("prepare grant-bound refresh fixture: %v (%s)", err, statement)
		}
	}

	repo := NewOAuthRepository(db)
	var grant ports.AuthorizationGrant
	withAuthorizationClientLockOnTiDB(t, db, "client-1", func(ctx context.Context) error {
		var err error
		grant, err = repo.EnsureAuthorizationGrant(ctx, ports.AuthorizationGrantRequest{
			SubjectID: "subject-1", ClientID: "client-1", Scope: "records.read", PolicyVersion: 7,
		})
		return err
	})
	if grant.ID == "" {
		t.Fatal("created authorization grant has no id")
	}

	if _, err := db.Exec(`INSERT INTO _System_OAuthRefreshToken (
		Id, TokenHash, UserId, ConnectedAppId, FamilyId, ConsentId, Scope, ExpiresAt, IsRevoked, CreatedDate, LastModifiedDate
	) VALUES ('refresh-1', 'presented-hash', 'subject-1', 'client-1', 'family-1', ?, 'records.read', DATE_ADD(NOW(), INTERVAL 1 DAY), 0, NOW(), NOW())`, grant.ID); err != nil {
		t.Fatalf("insert presented refresh token: %v", err)
	}

	withAuthorizationClientLockOnTiDB(t, db, "client-1", func(ctx context.Context) error {
		active, err := repo.AuthorizationGrantActive(ctx, grant.ID, "subject-1", "client-1", 7)
		if err != nil || !active {
			return fmt.Errorf("grant must be active before rotation: active=%t err=%v", active, err)
		}
		family := "family-1"
		clientID := "client-1"
		grantID := grant.ID
		rotated, reuse, err := repo.RotateRefreshToken(ctx, "presented-hash", &models.SystemOAuthRefreshToken{
			ID:             "refresh-2",
			TokenHash:      "successor-hash",
			UserID:         "subject-1",
			ConnectedAppID: &clientID,
			FamilyID:       &family,
			ConsentID:      &grantID,
			ExpiresAt:      time.Now().Add(time.Hour),
		})
		if err != nil || !rotated || reuse {
			return fmt.Errorf("grant-bound rotation = (%t,%t,%v), want (true,false,nil)", rotated, reuse, err)
		}
		return nil
	})

	var presentedRevoked, successorCount int
	if err := db.QueryRow(`SELECT IsRevoked FROM _System_OAuthRefreshToken WHERE Id = 'refresh-1'`).Scan(&presentedRevoked); err != nil {
		t.Fatalf("read presented refresh token: %v", err)
	}
	if err := db.QueryRow(`SELECT COUNT(*) FROM _System_OAuthRefreshToken WHERE Id = 'refresh-2' AND ConsentId = ?`, grant.ID).Scan(&successorCount); err != nil {
		t.Fatalf("read successor refresh token: %v", err)
	}
	if presentedRevoked != 1 || successorCount != 1 {
		t.Fatalf("rotation did not commit atomically: presentedRevoked=%d successorCount=%d", presentedRevoked, successorCount)
	}

	withAuthorizationClientLockOnTiDB(t, db, "client-1", func(ctx context.Context) error {
		_, err := repo.RevokeAuthorizationGrantsForSubjectAndClient(ctx, "subject-1", "client-1", "test:disconnect")
		return err
	})
	withAuthorizationClientLockOnTiDB(t, db, "client-1", func(ctx context.Context) error {
		active, err := repo.AuthorizationGrantActive(ctx, grant.ID, "subject-1", "client-1", 7)
		if err != nil {
			return err
		}
		if active {
			return fmt.Errorf("revoked authorization grant remained active")
		}
		return nil
	})

}
