package catalogapp

import (
	"bytes"
	"context"
	"crypto/ed25519"
	"crypto/sha256"
	"database/sql"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/pingcap-inc/nexuscrm-controlplane/componentbehavior"
	"github.com/pingcap-inc/nexuscrm-controlplane/internal/catalogadmissionfence"
	"github.com/pingcap-inc/nexuscrm-controlplane/releaseartifact"
)

// TransitionPublisherTrust applies one operator-owned key transition and its
// retry evidence in the same transaction. OperationID is the durable identity:
// an exact retry returns the committed historical receipt, while any reuse with
// different intent fails closed.
func (a *Authority) TransitionPublisherTrust(
	ctx context.Context, request PublisherTrustTransitionRequest,
) (PublisherTrustTransitionResult, error) {
	var zero PublisherTrustTransitionResult
	if strings.TrimSpace(request.OperationID) == "" ||
		strings.TrimSpace(request.OperationID) != request.OperationID ||
		len(request.OperationID) > 255 {
		return zero, errors.New("publisher trust transition operation ID is invalid")
	}
	expected, err := canonicalPublisherTrust(request.ExpectedTrust)
	if err != nil {
		return zero, fmt.Errorf("expected publisher trust: %w", err)
	}
	target, err := canonicalPublisherTrust(request.TargetTrust)
	if err != nil {
		return zero, fmt.Errorf("target publisher trust: %w", err)
	}
	if expected.PublisherID != target.PublisherID {
		return zero, errors.New("publisher trust transition changes publisher identity")
	}
	for index, key := range target.Keys {
		if key.Algorithm != releaseartifact.SignatureAlgorithm ||
			len(key.PublicKey) != ed25519.PublicKeySize {
			return zero, fmt.Errorf(
				"target publisher key[%d] is not a supported AppRelease verification key",
				index,
			)
		}
	}
	previousDigest, err := PublisherTrustDigest(expected)
	if err != nil {
		return zero, err
	}
	targetDigest, err := PublisherTrustDigest(target)
	if err != nil {
		return zero, err
	}
	if previousDigest == targetDigest {
		return zero, errors.New("publisher trust transition target equals expected trust")
	}
	resultEpoch := request.ExpectedOwnershipEpoch + 1
	if resultEpoch == 0 {
		return zero, errors.New("publisher ownership epoch exhausted")
	}
	intent := PublisherTrustTransitionResult{
		OperationID: request.OperationID, PublisherID: expected.PublisherID,
		ExpectedOwnershipEpoch: request.ExpectedOwnershipEpoch,
		OwnershipEpoch:         resultEpoch, PreviousTrustDigest: previousDigest,
		ActiveTrustDigest: targetDigest,
	}

	tx, err := a.db.BeginTx(ctx, nil)
	if err != nil {
		return zero, err
	}
	defer rollback(tx)
	if _, err := catalogadmissionfence.LockExclusive(ctx, tx); err != nil {
		return zero, fmt.Errorf("%w: publication admission fence: %v", ErrCorrupt, err)
	}
	committed, found, err := readPublisherTrustTransitionTx(ctx, tx, request.OperationID)
	if err != nil {
		return zero, err
	}
	if found {
		if committed != intent {
			return zero, ErrConflict
		}
		return committed, nil
	}
	current, err := readPublisherForUpdate(ctx, tx, expected.PublisherID)
	if err != nil {
		return zero, err
	}
	if current.OwnershipEpoch != request.ExpectedOwnershipEpoch ||
		!registeredPublisherMatchesTrust(current, expected) {
		return zero, ErrConflict
	}
	if _, err := applyPublisherTrustTx(
		ctx, tx, target, request.ExpectedOwnershipEpoch,
	); err != nil {
		return zero, err
	}
	if _, err := tx.ExecContext(ctx, `INSERT INTO _System_CatalogPublisherTrustTransition
(OperationId, PublisherId, ExpectedOwnershipEpoch, ResultOwnershipEpoch,
 PreviousTrustDigest, ActiveTrustDigest, CreatedDate)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP(6))`,
		intent.OperationID, intent.PublisherID, epochText(intent.ExpectedOwnershipEpoch),
		epochText(intent.OwnershipEpoch), intent.PreviousTrustDigest,
		intent.ActiveTrustDigest,
	); err != nil {
		if isDuplicate(err) {
			return zero, ErrConflict
		}
		return zero, err
	}
	if err := tx.Commit(); err != nil {
		return zero, err
	}
	return intent, nil
}

func readPublisherTrustTransitionTx(
	ctx context.Context, tx *sql.Tx, operationID string,
) (PublisherTrustTransitionResult, bool, error) {
	var result PublisherTrustTransitionResult
	var expectedRaw, resultRaw []byte
	err := tx.QueryRowContext(ctx, `SELECT OperationId, PublisherId,
ExpectedOwnershipEpoch, ResultOwnershipEpoch, PreviousTrustDigest, ActiveTrustDigest
FROM _System_CatalogPublisherTrustTransition WHERE OperationId = ? FOR UPDATE`,
		operationID,
	).Scan(&result.OperationID, &result.PublisherID, &expectedRaw, &resultRaw,
		&result.PreviousTrustDigest, &result.ActiveTrustDigest)
	if errors.Is(err, sql.ErrNoRows) {
		return result, false, nil
	}
	if err != nil {
		return result, false, err
	}
	if result.ExpectedOwnershipEpoch, err = parseEpoch(expectedRaw); err != nil {
		return result, false, fmt.Errorf("publisher transition expected epoch: %w", err)
	}
	if result.OwnershipEpoch, err = parseEpoch(resultRaw); err != nil {
		return result, false, fmt.Errorf("publisher transition result epoch: %w", err)
	}
	return result, true, nil
}

func readPublisherForUpdate(
	ctx context.Context, tx *sql.Tx, publisherID string,
) (RegisteredPublisher, error) {
	var result RegisteredPublisher
	var epochRaw []byte
	var state string
	err := tx.QueryRowContext(ctx, `SELECT PublisherId, OwnershipEpoch, State
FROM _System_CatalogPublisher WHERE PublisherId = ? FOR UPDATE`, publisherID).Scan(
		&result.PublisherID, &epochRaw, &state,
	)
	if errors.Is(err, sql.ErrNoRows) {
		return result, ErrNotFound
	}
	if err != nil {
		return result, err
	}
	result.OwnershipEpoch, err = parseEpoch(epochRaw)
	if err != nil {
		return result, err
	}
	result.Active = state == "ACTIVE"
	rows, err := tx.QueryContext(ctx, `SELECT KeyId, Algorithm, PublicKey,
ValidFrom, ValidUntil, State FROM _System_CatalogPublisherKey
WHERE PublisherId = ? ORDER BY KeyId FOR UPDATE`, publisherID)
	if err != nil {
		return result, err
	}
	defer rows.Close()
	for rows.Next() {
		var key RegisteredPublisherKey
		var keyState string
		if err := rows.Scan(
			&key.KeyID, &key.Algorithm, &key.PublicKey, &key.ValidFrom,
			&key.ValidUntil, &keyState,
		); err != nil {
			return result, err
		}
		key.Active = keyState == "ACTIVE"
		result.Keys = append(result.Keys, key)
	}
	return result, rows.Err()
}

func registeredPublisherMatchesTrust(
	existing RegisteredPublisher, trust PublisherTrust,
) bool {
	if existing.PublisherID != trust.PublisherID || existing.Active != trust.Active {
		return false
	}
	active := make([]PublisherKey, 0, len(existing.Keys))
	for _, key := range existing.Keys {
		if key.Active {
			active = append(active, key.PublisherKey)
		}
	}
	sort.Slice(active, func(i, j int) bool { return active[i].KeyID < active[j].KeyID })
	if len(active) != len(trust.Keys) {
		return false
	}
	for index := range active {
		if active[index].KeyID != trust.Keys[index].KeyID ||
			active[index].Algorithm != trust.Keys[index].Algorithm ||
			!bytes.Equal(active[index].PublicKey, trust.Keys[index].PublicKey) ||
			!active[index].ValidFrom.Equal(trust.Keys[index].ValidFrom) ||
			!active[index].ValidUntil.Equal(trust.Keys[index].ValidUntil) {
			return false
		}
	}
	return true
}

func PublisherTrustDigest(trust PublisherTrust) (string, error) {
	canonicalTrust, err := canonicalPublisherTrust(trust)
	if err != nil {
		return "", err
	}
	type canonicalKey struct {
		KeyID           string `json:"keyId"`
		Algorithm       string `json:"algorithm"`
		PublicKeyBase64 string `json:"publicKeyBase64"`
		ValidFrom       string `json:"validFrom"`
		ValidUntil      string `json:"validUntil"`
	}
	payload := struct {
		PublisherID string         `json:"publisherId"`
		Active      bool           `json:"active"`
		Keys        []canonicalKey `json:"keys"`
	}{PublisherID: canonicalTrust.PublisherID, Active: canonicalTrust.Active}
	for _, key := range canonicalTrust.Keys {
		payload.Keys = append(payload.Keys, canonicalKey{
			KeyID: key.KeyID, Algorithm: key.Algorithm,
			PublicKeyBase64: base64.StdEncoding.EncodeToString(key.PublicKey),
			ValidFrom:       key.ValidFrom.UTC().Format(time.RFC3339Nano),
			ValidUntil:      key.ValidUntil.UTC().Format(time.RFC3339Nano),
		})
	}
	raw, err := json.Marshal(payload)
	if err != nil {
		return "", err
	}
	canonical, err := componentbehavior.CanonicalJSONV1(raw)
	if err != nil {
		return "", fmt.Errorf("canonicalize Catalog publisher trust digest: %w", err)
	}
	sum := sha256.Sum256(canonical)
	return "sha256:" + hex.EncodeToString(sum[:]), nil
}
