package catalogverificationtx

import (
	"bytes"
	"context"
	"crypto/sha256"
	"database/sql"
	"encoding/binary"
	"encoding/hex"
	"errors"
	"fmt"

	layoutbundlekeyv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/layoutbundlekeyv1"
	tenantauthorityv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/tenantlayoutbundleauthorityv1"
)

const tenantOutcomeDigestDomain = "nexuscrm.catalog-tenant-authority-outcome/v1"

type tenantCreationHistory struct {
	commandID tenantauthorityv1.CommandID
	sequence  uint64
}

type tenantReference struct {
	present bool
	digest  string
}

type tenantCommand struct {
	commandID         tenantauthorityv1.CommandID
	kind              string
	requestDigest     string
	result            string
	observedPrior     tenantReference
	resultingValue    tenantReference
	observedNamespace tenantReference
	observedBinding   tenantReference
	sequence          uint64
	outcomeDigest     string
}

func verifyTenantOwnerLineage(
	ctx context.Context,
	tx *sql.Tx,
	request TenantOwnerRequest,
	namespace tenantNamespaceCurrent,
	binding tenantBindingCurrent,
	slot tenantSlotCurrent,
) (uint64, error) {
	namespaceHistory, err := readExactActiveNamespaceHistory(
		ctx,
		tx,
		namespace,
	)
	if err != nil {
		return 0, err
	}
	bindingHistory, err := readExactActiveBindingHistory(ctx, tx, binding)
	if err != nil {
		return 0, err
	}
	slotHistory, err := readExactActiveSlotHistory(ctx, tx, slot)
	if err != nil {
		return 0, err
	}
	if namespaceHistory.sequence >= bindingHistory.sequence ||
		bindingHistory.sequence >= slotHistory.sequence {
		return 0, fmt.Errorf(
			"%w: tenant creation sequence must be namespace before binding before slot",
			ErrTenantOwnerCorrupt,
		)
	}

	namespaceRequest := tenantauthorityv1.TenantNamespaceCommandRequest{
		Operation:               tenantauthorityv1.NamespaceOperationReserve,
		TargetTenantNamespaceID: namespace.value.TenantNamespaceID,
		DesiredNamespaceValue:   namespace.value,
		ExpectedPriorNamespace: tenantauthorityv1.TenantNamespaceExpectedPrior{
			Variant: tenantauthorityv1.NamespaceExpectedPriorAbsent,
		},
	}
	namespaceRequestDigest, err :=
		tenantauthorityv1.NamespaceCommandRequestDigest(namespaceRequest)
	if err != nil {
		return 0, fmt.Errorf(
			"%w: reconstruct namespace creation request: %v",
			ErrTenantOwnerCorrupt,
			err,
		)
	}
	if err := verifyTenantCreationCommand(ctx, tx, tenantCommand{
		commandID:     namespaceHistory.commandID,
		kind:          "NAMESPACE",
		requestDigest: namespaceRequestDigest,
		result:        "APPLIED",
		resultingValue: tenantReference{
			present: true,
			digest:  namespace.digest,
		},
		sequence: namespaceHistory.sequence,
	}); err != nil {
		return 0, err
	}

	bindingRequest := tenantauthorityv1.TenantOrgNamespaceBindingCommandRequest{
		Operation:                          tenantauthorityv1.BindingOperationBind,
		TargetOrgID:                        request.OrgID,
		DesiredBindingValue:                binding.value,
		ExpectedNamespaceReservationDigest: namespace.digest,
		ExpectedPriorBinding: tenantauthorityv1.TenantOrgBindingExpectedPrior{
			Variant: tenantauthorityv1.BindingExpectedPriorAbsent,
		},
	}
	bindingRequestDigest, err :=
		tenantauthorityv1.OrgBindingCommandRequestDigest(bindingRequest)
	if err != nil {
		return 0, fmt.Errorf(
			"%w: reconstruct binding creation request: %v",
			ErrTenantOwnerCorrupt,
			err,
		)
	}
	if err := verifyTenantCreationCommand(ctx, tx, tenantCommand{
		commandID:     bindingHistory.commandID,
		kind:          "ORG_BINDING",
		requestDigest: bindingRequestDigest,
		result:        "APPLIED",
		resultingValue: tenantReference{
			present: true,
			digest:  binding.digest,
		},
		observedNamespace: tenantReference{
			present: true,
			digest:  namespace.digest,
		},
		sequence: bindingHistory.sequence,
	}); err != nil {
		return 0, err
	}

	slotRequest := tenantauthorityv1.TenantLayoutBundleSlotCommandRequest{
		Operation:                           tenantauthorityv1.SlotOperationCreate,
		AuthorizingOrgID:                    request.OrgID,
		TargetBundleKey:                     slot.value.BundleKey,
		DesiredSlotValue:                    slot.value,
		ExpectedNamespaceReservationDigest:  namespace.digest,
		ExpectedAuthorizingOrgBindingDigest: binding.digest,
		ExpectedPriorSlot: tenantauthorityv1.TenantSlotExpectedPrior{
			Variant: tenantauthorityv1.SlotExpectedPriorAbsent,
		},
	}
	slotRequestDigest, err :=
		tenantauthorityv1.SlotCommandRequestDigest(slotRequest)
	if err != nil {
		return 0, fmt.Errorf(
			"%w: reconstruct slot creation request: %v",
			ErrTenantOwnerCorrupt,
			err,
		)
	}
	if err := verifyTenantCreationCommand(ctx, tx, tenantCommand{
		commandID:     slotHistory.commandID,
		kind:          "SLOT",
		requestDigest: slotRequestDigest,
		result:        "APPLIED",
		resultingValue: tenantReference{
			present: true,
			digest:  slot.digest,
		},
		observedNamespace: tenantReference{
			present: true,
			digest:  namespace.digest,
		},
		observedBinding: tenantReference{
			present: true,
			digest:  binding.digest,
		},
		sequence: slotHistory.sequence,
	}); err != nil {
		return 0, err
	}

	if err := rejectTenantRetirementOutcome(
		ctx,
		tx,
		namespace.value,
		binding.value,
		slot.value,
	); err != nil {
		return 0, err
	}
	return slotHistory.sequence, nil
}

func readExactActiveNamespaceHistory(
	ctx context.Context,
	tx *sql.Tx,
	current tenantNamespaceCurrent,
) (tenantCreationHistory, error) {
	rows, err := tx.QueryContext(ctx, `SELECT CommandId, CommandSequence,
TenantNamespaceId, ValueVersion, State, ValueDigest, PriorValueDigest
FROM _System_CatalogTenantNamespaceHistory
WHERE TenantNamespaceId = ? ORDER BY CommandSequence FOR UPDATE`,
		current.value.TenantNamespaceID,
	)
	if err != nil {
		return tenantCreationHistory{}, err
	}
	defer rows.Close()

	var history tenantCreationHistory
	var storedCommandID, rawState string
	var rawSequence, storedNamespaceID []byte
	var storedValue tenantauthorityv1.TenantNamespaceReservationV1
	var storedDigest string
	var prior sql.NullString
	if !rows.Next() {
		if err := rows.Err(); err != nil {
			return tenantCreationHistory{}, err
		}
		return tenantCreationHistory{}, fmt.Errorf(
			"%w: tenant namespace creation history is absent",
			ErrTenantOwnerCorrupt,
		)
	}
	if err := rows.Scan(
		&storedCommandID,
		&rawSequence,
		&storedNamespaceID,
		&storedValue.ValueVersion,
		&rawState,
		&storedDigest,
		&prior,
	); err != nil {
		return tenantCreationHistory{}, err
	}
	if rows.Next() {
		return tenantCreationHistory{}, fmt.Errorf(
			"%w: tenant namespace has multiple history rows",
			ErrTenantOwnerCorrupt,
		)
	}
	if err := rows.Err(); err != nil {
		return tenantCreationHistory{}, err
	}
	storedValue.TenantNamespaceID = string(storedNamespaceID)
	storedValue.State = tenantauthorityv1.NamespaceState(rawState)
	history.commandID = tenantauthorityv1.CommandID(storedCommandID)
	history.sequence, err = parseTenantUint(
		"namespace history CommandSequence",
		rawSequence,
		false,
	)
	if err != nil {
		return tenantCreationHistory{}, err
	}
	computed, digestErr := tenantauthorityv1.NamespaceValueDigest(storedValue)
	if digestErr != nil ||
		computed != storedDigest ||
		prior.Valid ||
		storedValue != current.value ||
		storedDigest != current.digest ||
		history.commandID != current.lastCommandID {
		return tenantCreationHistory{}, fmt.Errorf(
			"%w: tenant namespace creation history differs from current",
			ErrTenantOwnerCorrupt,
		)
	}
	return history, nil
}

func readExactActiveBindingHistory(
	ctx context.Context,
	tx *sql.Tx,
	current tenantBindingCurrent,
) (tenantCreationHistory, error) {
	rows, err := tx.QueryContext(ctx, `SELECT CommandId, CommandSequence,
OrgId, TenantNamespaceId, ValueVersion, State, ValueDigest, PriorValueDigest
FROM _System_CatalogTenantOrgNamespaceBindingHistory
WHERE OrgId = ? ORDER BY CommandSequence FOR UPDATE`,
		current.value.OrgID,
	)
	if err != nil {
		return tenantCreationHistory{}, err
	}
	defer rows.Close()

	var history tenantCreationHistory
	var storedCommandID, rawState string
	var rawSequence, storedOrgID, storedNamespaceID []byte
	var storedValue tenantauthorityv1.TenantOrgNamespaceBindingV1
	var storedDigest string
	var prior sql.NullString
	if !rows.Next() {
		if err := rows.Err(); err != nil {
			return tenantCreationHistory{}, err
		}
		return tenantCreationHistory{}, fmt.Errorf(
			"%w: tenant binding creation history is absent",
			ErrTenantOwnerCorrupt,
		)
	}
	if err := rows.Scan(
		&storedCommandID,
		&rawSequence,
		&storedOrgID,
		&storedNamespaceID,
		&storedValue.ValueVersion,
		&rawState,
		&storedDigest,
		&prior,
	); err != nil {
		return tenantCreationHistory{}, err
	}
	if rows.Next() {
		return tenantCreationHistory{}, fmt.Errorf(
			"%w: tenant binding has multiple history rows",
			ErrTenantOwnerCorrupt,
		)
	}
	if err := rows.Err(); err != nil {
		return tenantCreationHistory{}, err
	}
	storedValue.OrgID = string(storedOrgID)
	storedValue.TenantNamespaceID = string(storedNamespaceID)
	storedValue.State = tenantauthorityv1.OrgBindingState(rawState)
	history.commandID = tenantauthorityv1.CommandID(storedCommandID)
	history.sequence, err = parseTenantUint(
		"binding history CommandSequence",
		rawSequence,
		false,
	)
	if err != nil {
		return tenantCreationHistory{}, err
	}
	computed, digestErr := tenantauthorityv1.OrgBindingValueDigest(storedValue)
	if digestErr != nil ||
		computed != storedDigest ||
		prior.Valid ||
		storedValue != current.value ||
		storedDigest != current.digest ||
		history.commandID != current.lastCommandID {
		return tenantCreationHistory{}, fmt.Errorf(
			"%w: tenant binding creation history differs from current",
			ErrTenantOwnerCorrupt,
		)
	}
	return history, nil
}

func readExactActiveSlotHistory(
	ctx context.Context,
	tx *sql.Tx,
	current tenantSlotCurrent,
) (tenantCreationHistory, error) {
	rows, err := tx.QueryContext(ctx, `SELECT CommandId, CommandSequence,
TenantNamespaceId, LocalComponentKey, BundleKeyVersion, SlotOwnerKind,
ValueVersion, State, ValueDigest, PriorValueDigest
FROM _System_CatalogTenantLayoutBundleSlotHistory
WHERE TenantNamespaceId = ? AND LocalComponentKey = ? ORDER BY CommandSequence FOR UPDATE`,
		current.value.BundleKey.SlotOwnerNamespace,
		string(current.value.BundleKey.LocalComponentKey),
	)
	if err != nil {
		return tenantCreationHistory{}, err
	}
	defer rows.Close()

	var history tenantCreationHistory
	var storedCommandID, rawOwnerKind, rawState string
	var rawSequence, storedNamespaceID, storedLocalKey []byte
	var storedValue tenantauthorityv1.TenantLayoutBundleSlotV1
	var storedDigest string
	var prior sql.NullString
	if !rows.Next() {
		if err := rows.Err(); err != nil {
			return tenantCreationHistory{}, err
		}
		return tenantCreationHistory{}, fmt.Errorf(
			"%w: tenant slot creation history is absent",
			ErrTenantOwnerCorrupt,
		)
	}
	if err := rows.Scan(
		&storedCommandID,
		&rawSequence,
		&storedNamespaceID,
		&storedLocalKey,
		&storedValue.BundleKey.BundleKeyVersion,
		&rawOwnerKind,
		&storedValue.ValueVersion,
		&rawState,
		&storedDigest,
		&prior,
	); err != nil {
		return tenantCreationHistory{}, err
	}
	if rows.Next() {
		return tenantCreationHistory{}, fmt.Errorf(
			"%w: tenant slot has multiple history rows",
			ErrTenantOwnerCorrupt,
		)
	}
	if err := rows.Err(); err != nil {
		return tenantCreationHistory{}, err
	}
	storedValue.BundleKey.SlotOwnerKind = layoutbundlekeyv1.SlotOwnerKind(rawOwnerKind)
	storedValue.BundleKey.SlotOwnerNamespace = string(storedNamespaceID)
	storedValue.BundleKey.LocalComponentKey = layoutbundlekeyv1.LocalComponentKey(
		storedLocalKey,
	)
	storedValue.State = tenantauthorityv1.SlotState(rawState)
	history.commandID = tenantauthorityv1.CommandID(storedCommandID)
	history.sequence, err = parseTenantUint(
		"slot history CommandSequence",
		rawSequence,
		false,
	)
	if err != nil {
		return tenantCreationHistory{}, err
	}
	computed, digestErr := tenantauthorityv1.SlotValueDigest(storedValue)
	if digestErr != nil ||
		computed != storedDigest ||
		prior.Valid ||
		storedValue != current.value ||
		storedDigest != current.digest ||
		history.commandID != current.lastCommandID {
		return tenantCreationHistory{}, fmt.Errorf(
			"%w: tenant slot creation history differs from current",
			ErrTenantOwnerCorrupt,
		)
	}
	return history, nil
}

func verifyTenantCreationCommand(
	ctx context.Context,
	tx *sql.Tx,
	expected tenantCommand,
) error {
	actual, err := readExactTenantCommand(ctx, tx, expected.commandID)
	if err != nil {
		return err
	}
	expected.outcomeDigest = actual.outcomeDigest
	if actual != expected {
		return fmt.Errorf(
			"%w: tenant creation command differs from its value history",
			ErrTenantOwnerCorrupt,
		)
	}
	computed, err := tenantCommandOutcomeDigest(actual)
	if err != nil || computed != actual.outcomeDigest {
		return fmt.Errorf(
			"%w: tenant creation command outcome digest differs",
			ErrTenantOwnerCorrupt,
		)
	}
	return nil
}

func readExactTenantCommand(
	ctx context.Context,
	tx *sql.Tx,
	commandID tenantauthorityv1.CommandID,
) (tenantCommand, error) {
	rows, err := tx.QueryContext(ctx, `SELECT CommandId, CommandKind, RequestDigest, Result,
ObservedPriorPresent, ObservedPriorDigest, ResultingValuePresent, ResultingValueDigest,
ObservedNamespacePresent, ObservedNamespaceDigest,
ObservedBindingPresent, ObservedBindingDigest,
CommandSequence, OutcomeDigest
FROM _System_CatalogTenantAuthorityCommand WHERE CommandId = ? FOR UPDATE`,
		string(commandID),
	)
	if err != nil {
		return tenantCommand{}, err
	}
	defer rows.Close()
	if !rows.Next() {
		if err := rows.Err(); err != nil {
			return tenantCommand{}, err
		}
		return tenantCommand{}, fmt.Errorf(
			"%w: tenant creation command is absent",
			ErrTenantOwnerCorrupt,
		)
	}

	var command tenantCommand
	var storedCommandID string
	var priorPresent, resultingPresent, namespacePresent, bindingPresent bool
	var priorDigest, resultingDigest, namespaceDigest, bindingDigest sql.NullString
	var rawSequence []byte
	if err := rows.Scan(
		&storedCommandID,
		&command.kind,
		&command.requestDigest,
		&command.result,
		&priorPresent,
		&priorDigest,
		&resultingPresent,
		&resultingDigest,
		&namespacePresent,
		&namespaceDigest,
		&bindingPresent,
		&bindingDigest,
		&rawSequence,
		&command.outcomeDigest,
	); err != nil {
		return tenantCommand{}, err
	}
	if rows.Next() {
		return tenantCommand{}, fmt.Errorf(
			"%w: tenant creation command identity is ambiguous",
			ErrTenantOwnerCorrupt,
		)
	}
	if err := rows.Err(); err != nil {
		return tenantCommand{}, err
	}
	if storedCommandID != string(commandID) {
		return tenantCommand{}, fmt.Errorf(
			"%w: tenant creation command identity differs",
			ErrTenantOwnerCorrupt,
		)
	}
	command.commandID = tenantauthorityv1.CommandID(storedCommandID)
	if err := tenantauthorityv1.ValidateCommandID(command.commandID); err != nil {
		return tenantCommand{}, fmt.Errorf(
			"%w: tenant creation CommandId is invalid",
			ErrTenantOwnerCorrupt,
		)
	}
	command.observedPrior, err = tenantDurableReference(
		"ObservedPrior",
		priorPresent,
		priorDigest,
	)
	if err != nil {
		return tenantCommand{}, err
	}
	command.resultingValue, err = tenantDurableReference(
		"ResultingValue",
		resultingPresent,
		resultingDigest,
	)
	if err != nil {
		return tenantCommand{}, err
	}
	command.observedNamespace, err = tenantDurableReference(
		"ObservedNamespace",
		namespacePresent,
		namespaceDigest,
	)
	if err != nil {
		return tenantCommand{}, err
	}
	command.observedBinding, err = tenantDurableReference(
		"ObservedBinding",
		bindingPresent,
		bindingDigest,
	)
	if err != nil {
		return tenantCommand{}, err
	}
	command.sequence, err = parseTenantUint(
		"creation command CommandSequence",
		rawSequence,
		false,
	)
	if err != nil {
		return tenantCommand{}, err
	}
	return command, nil
}

func tenantDurableReference(
	label string,
	present bool,
	digest sql.NullString,
) (tenantReference, error) {
	if present != digest.Valid {
		return tenantReference{}, fmt.Errorf(
			"%w: %s presence and NULL state disagree",
			ErrTenantOwnerCorrupt,
			label,
		)
	}
	if !present {
		return tenantReference{}, nil
	}
	return tenantReference{present: true, digest: digest.String}, nil
}

func rejectTenantRetirementOutcome(
	ctx context.Context,
	tx *sql.Tx,
	namespace tenantauthorityv1.TenantNamespaceReservationV1,
	binding tenantauthorityv1.TenantOrgNamespaceBindingV1,
	slot tenantauthorityv1.TenantLayoutBundleSlotV1,
) error {
	namespace.State = tenantauthorityv1.NamespaceStateTombstone
	namespaceDigest, err := tenantauthorityv1.NamespaceValueDigest(namespace)
	if err != nil {
		return fmt.Errorf("%w: derive namespace tombstone", ErrTenantOwnerCorrupt)
	}
	binding.State = tenantauthorityv1.OrgBindingStateTombstone
	bindingDigest, err := tenantauthorityv1.OrgBindingValueDigest(binding)
	if err != nil {
		return fmt.Errorf("%w: derive binding tombstone", ErrTenantOwnerCorrupt)
	}
	slot.State = tenantauthorityv1.SlotStateTombstone
	slotDigest, err := tenantauthorityv1.SlotValueDigest(slot)
	if err != nil {
		return fmt.Errorf("%w: derive slot tombstone", ErrTenantOwnerCorrupt)
	}

	rows, err := tx.QueryContext(ctx, `SELECT CommandId
FROM _System_CatalogTenantAuthorityCommand
WHERE Result = 'APPLIED' AND ResultingValuePresent = TRUE
AND ResultingValueDigest IN (?, ?, ?)
ORDER BY CommandSequence FOR UPDATE`,
		namespaceDigest,
		bindingDigest,
		slotDigest,
	)
	if err != nil {
		return err
	}
	defer rows.Close()
	if rows.Next() {
		return fmt.Errorf(
			"%w: ACTIVE tenant owner triple has a successful retirement outcome",
			ErrTenantOwnerCorrupt,
		)
	}
	return rows.Err()
}

func tenantCommandOutcomeDigest(command tenantCommand) (string, error) {
	if command.commandID == "" ||
		command.kind == "" ||
		command.requestDigest == "" ||
		command.result == "" ||
		command.sequence == 0 {
		return "", errors.New("tenant command is incomplete")
	}
	var canonical bytes.Buffer
	writeTenantString(&canonical, tenantOutcomeDigestDomain)
	writeTenantString(&canonical, command.kind)
	writeTenantString(&canonical, string(command.commandID))
	writeTenantString(&canonical, command.requestDigest)
	writeTenantString(&canonical, command.result)
	writeTenantReference(&canonical, command.observedPrior)
	writeTenantReference(&canonical, command.resultingValue)
	writeTenantReference(&canonical, command.observedNamespace)
	writeTenantReference(&canonical, command.observedBinding)
	writeTenantUint(&canonical, command.sequence)
	digest := sha256.Sum256(canonical.Bytes())
	return "sha256:" + hex.EncodeToString(digest[:]), nil
}

func writeTenantReference(out *bytes.Buffer, reference tenantReference) {
	if !reference.present {
		out.WriteByte(0)
		return
	}
	out.WriteByte(1)
	writeTenantString(out, reference.digest)
}

func writeTenantString(out *bytes.Buffer, value string) {
	writeTenantUint(out, uint64(len(value)))
	out.WriteString(value)
}

func writeTenantUint(out *bytes.Buffer, value uint64) {
	var encoded [binary.MaxVarintLen64]byte
	count := binary.PutUvarint(encoded[:], value)
	out.Write(encoded[:count])
}
