package catalogapp

import (
	"context"
	"crypto/ed25519"
	"errors"
	"regexp"
	"testing"
	"time"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/pingcap-inc/nexuscrm-controlplane/internal/catalogadmissionfence"
	"github.com/pingcap-inc/nexuscrm-controlplane/releaseartifact"
)

func publisherTransitionFixture(t *testing.T) PublisherTrustTransitionRequest {
	t.Helper()
	validFrom := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
	validUntil := time.Date(2036, 1, 1, 0, 0, 0, 0, time.UTC)
	return PublisherTrustTransitionRequest{
		OperationID:            "test2-publisher-transition",
		ExpectedOwnershipEpoch: 4,
		ExpectedTrust: PublisherTrust{PublisherID: "io.nexuscrm", Active: true, Keys: []PublisherKey{{
			KeyID: "old", Algorithm: releaseartifact.SignatureAlgorithm,
			PublicKey: make([]byte, ed25519.PublicKeySize), ValidFrom: validFrom, ValidUntil: validUntil,
		}}},
		TargetTrust: PublisherTrust{PublisherID: "io.nexuscrm", Active: true, Keys: []PublisherKey{{
			KeyID: "new", Algorithm: releaseartifact.SignatureAlgorithm,
			PublicKey: append([]byte{1}, make([]byte, ed25519.PublicKeySize-1)...),
			ValidFrom: validFrom, ValidUntil: validUntil,
		}}},
	}
}

func TestPublisherTrustTransitionCommitsAuthorityAndReceiptAtomically(t *testing.T) {
	db, mockDB, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	authority, err := New(db, Options{})
	if err != nil {
		t.Fatal(err)
	}
	request := publisherTransitionFixture(t)
	previousDigest, err := PublisherTrustDigest(request.ExpectedTrust)
	if err != nil {
		t.Fatal(err)
	}
	targetDigest, err := PublisherTrustDigest(request.TargetTrust)
	if err != nil {
		t.Fatal(err)
	}

	mockDB.ExpectBegin()
	mockDB.ExpectQuery(regexp.QuoteMeta(`SELECT FenceKey, FenceGeneration
FROM _System_CatalogPublicationAdmissionFence
WHERE FenceKey = ?
FOR UPDATE`)).WithArgs(catalogadmissionfence.Key).WillReturnRows(
		sqlmock.NewRows([]string{"FenceKey", "FenceGeneration"}).AddRow(
			catalogadmissionfence.Key, []byte("1"),
		),
	)
	mockDB.ExpectQuery(regexp.QuoteMeta(`SELECT OperationId, PublisherId,
ExpectedOwnershipEpoch, ResultOwnershipEpoch, PreviousTrustDigest, ActiveTrustDigest
FROM _System_CatalogPublisherTrustTransition WHERE OperationId = ? FOR UPDATE`)).
		WithArgs(request.OperationID).
		WillReturnRows(sqlmock.NewRows([]string{
			"OperationId", "PublisherId", "ExpectedOwnershipEpoch", "ResultOwnershipEpoch",
			"PreviousTrustDigest", "ActiveTrustDigest",
		}))
	mockDB.ExpectQuery(regexp.QuoteMeta(`SELECT PublisherId, OwnershipEpoch, State
FROM _System_CatalogPublisher WHERE PublisherId = ? FOR UPDATE`)).
		WithArgs("io.nexuscrm").
		WillReturnRows(sqlmock.NewRows([]string{"PublisherId", "OwnershipEpoch", "State"}).
			AddRow("io.nexuscrm", []byte("4"), "ACTIVE"))
	mockDB.ExpectQuery(regexp.QuoteMeta(`SELECT KeyId, Algorithm, PublicKey,
ValidFrom, ValidUntil, State FROM _System_CatalogPublisherKey
WHERE PublisherId = ? ORDER BY KeyId FOR UPDATE`)).
		WithArgs("io.nexuscrm").
		WillReturnRows(sqlmock.NewRows([]string{
			"KeyId", "Algorithm", "PublicKey", "ValidFrom", "ValidUntil", "State",
		}).AddRow(
			"old", releaseartifact.SignatureAlgorithm, request.ExpectedTrust.Keys[0].PublicKey,
			request.ExpectedTrust.Keys[0].ValidFrom, request.ExpectedTrust.Keys[0].ValidUntil, "ACTIVE",
		))
	mockDB.ExpectExec(regexp.QuoteMeta(`UPDATE _System_CatalogPublisher
SET OwnershipEpoch = ?, State = ?, LastModifiedDate = CURRENT_TIMESTAMP(6)
WHERE PublisherId = ? AND OwnershipEpoch = ?`)).
		WithArgs("5", "ACTIVE", "io.nexuscrm", "4").
		WillReturnResult(sqlmock.NewResult(0, 1))
	mockDB.ExpectExec(regexp.QuoteMeta(`UPDATE _System_CatalogPublisherKey SET State = 'REVOKED', LastModifiedDate = CURRENT_TIMESTAMP(6)
WHERE PublisherId = ? AND State = 'ACTIVE'`)).
		WithArgs("io.nexuscrm").WillReturnResult(sqlmock.NewResult(0, 1))
	mockDB.ExpectExec(regexp.QuoteMeta(`INSERT INTO _System_CatalogPublisherKey
(PublisherId, KeyId, Algorithm, PublicKey, ValidFrom, ValidUntil, State, CreatedDate, LastModifiedDate)
VALUES (?, ?, ?, ?, ?, ?, 'ACTIVE', CURRENT_TIMESTAMP(6), CURRENT_TIMESTAMP(6))`)).
		WithArgs(
			"io.nexuscrm", "new", releaseartifact.SignatureAlgorithm,
			request.TargetTrust.Keys[0].PublicKey, request.TargetTrust.Keys[0].ValidFrom,
			request.TargetTrust.Keys[0].ValidUntil,
		).WillReturnResult(sqlmock.NewResult(1, 1))
	mockDB.ExpectExec(regexp.QuoteMeta(`INSERT INTO _System_CatalogPublisherTrustTransition
(OperationId, PublisherId, ExpectedOwnershipEpoch, ResultOwnershipEpoch,
 PreviousTrustDigest, ActiveTrustDigest, CreatedDate)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP(6))`)).
		WithArgs(request.OperationID, "io.nexuscrm", "4", "5", previousDigest, targetDigest).
		WillReturnResult(sqlmock.NewResult(1, 1))
	mockDB.ExpectCommit()

	result, err := authority.TransitionPublisherTrust(context.Background(), request)
	if err != nil {
		t.Fatal(err)
	}
	if result.OperationID != request.OperationID || result.OwnershipEpoch != 5 ||
		result.PreviousTrustDigest != previousDigest || result.ActiveTrustDigest != targetDigest {
		t.Fatalf("transition result = %#v", result)
	}
	if err := mockDB.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
}

func TestPublisherTrustTransitionRetryRequiresExactDurableOperation(t *testing.T) {
	db, mockDB, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	authority, err := New(db, Options{})
	if err != nil {
		t.Fatal(err)
	}
	request := publisherTransitionFixture(t)
	previousDigest, _ := PublisherTrustDigest(request.ExpectedTrust)
	targetDigest, _ := PublisherTrustDigest(request.TargetTrust)

	mockDB.ExpectBegin()
	mockDB.ExpectQuery(regexp.QuoteMeta(`SELECT FenceKey, FenceGeneration
FROM _System_CatalogPublicationAdmissionFence
WHERE FenceKey = ?
FOR UPDATE`)).WithArgs(catalogadmissionfence.Key).WillReturnRows(
		sqlmock.NewRows([]string{"FenceKey", "FenceGeneration"}).AddRow(
			catalogadmissionfence.Key, []byte("1"),
		),
	)
	mockDB.ExpectQuery(regexp.QuoteMeta(`SELECT OperationId, PublisherId,
ExpectedOwnershipEpoch, ResultOwnershipEpoch, PreviousTrustDigest, ActiveTrustDigest
FROM _System_CatalogPublisherTrustTransition WHERE OperationId = ? FOR UPDATE`)).
		WithArgs(request.OperationID).
		WillReturnRows(sqlmock.NewRows([]string{
			"OperationId", "PublisherId", "ExpectedOwnershipEpoch", "ResultOwnershipEpoch",
			"PreviousTrustDigest", "ActiveTrustDigest",
		}).AddRow(request.OperationID, "io.nexuscrm", []byte("4"), []byte("5"), previousDigest, targetDigest))
	mockDB.ExpectRollback()

	result, err := authority.TransitionPublisherTrust(context.Background(), request)
	if err != nil {
		t.Fatal(err)
	}
	if result.PreviousTrustDigest != previousDigest || result.ActiveTrustDigest != targetDigest {
		t.Fatalf("retry result = %#v", result)
	}
	if err := mockDB.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
}

func TestPublisherTrustTransitionRejectsUnusableTargetBeforeSQL(t *testing.T) {
	db, mockDB, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	authority, err := New(db, Options{})
	if err != nil {
		t.Fatal(err)
	}
	request := publisherTransitionFixture(t)
	request.TargetTrust.Keys[0].PublicKey = []byte("too-short")
	if _, err := authority.TransitionPublisherTrust(context.Background(), request); err == nil {
		t.Fatal("unusable target publisher key was accepted")
	}
	if err := mockDB.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
}

func TestPublisherTrustTransitionRejectsOperationIDReuseWithDifferentIntent(t *testing.T) {
	db, mockDB, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	authority, err := New(db, Options{})
	if err != nil {
		t.Fatal(err)
	}
	request := publisherTransitionFixture(t)
	previousDigest, _ := PublisherTrustDigest(request.ExpectedTrust)
	targetDigest, _ := PublisherTrustDigest(request.TargetTrust)
	request.TargetTrust.Keys[0].PublicKey[1] = 2

	mockDB.ExpectBegin()
	mockDB.ExpectQuery(regexp.QuoteMeta(`SELECT FenceKey, FenceGeneration
FROM _System_CatalogPublicationAdmissionFence
WHERE FenceKey = ?
FOR UPDATE`)).WithArgs(catalogadmissionfence.Key).WillReturnRows(
		sqlmock.NewRows([]string{"FenceKey", "FenceGeneration"}).AddRow(
			catalogadmissionfence.Key, []byte("1"),
		),
	)
	mockDB.ExpectQuery(regexp.QuoteMeta(`SELECT OperationId, PublisherId,
ExpectedOwnershipEpoch, ResultOwnershipEpoch, PreviousTrustDigest, ActiveTrustDigest
FROM _System_CatalogPublisherTrustTransition WHERE OperationId = ? FOR UPDATE`)).
		WithArgs(request.OperationID).
		WillReturnRows(sqlmock.NewRows([]string{
			"OperationId", "PublisherId", "ExpectedOwnershipEpoch", "ResultOwnershipEpoch",
			"PreviousTrustDigest", "ActiveTrustDigest",
		}).AddRow(request.OperationID, "io.nexuscrm", []byte("4"), []byte("5"), previousDigest, targetDigest))
	mockDB.ExpectRollback()

	if _, err := authority.TransitionPublisherTrust(
		context.Background(), request,
	); !errors.Is(err, ErrConflict) {
		t.Fatalf("operation ID reuse error = %v", err)
	}
	if err := mockDB.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
}
