package managedlifecycle

import (
	"bytes"
	"context"
	"crypto/sha256"
	"database/sql"
	"encoding/json"
	"fmt"
	"reflect"

	cp "github.com/pingcap-inc/nexuscrm-contract/pkg/controlplane"
)

const (
	managedLifecycleMaximumProjectionIdentityBytes = 1024
	managedLifecycleMaximumEffectReceiptBytes      = 64 << 10
	managedLifecycleMaximumEvidenceReceiptBytes    = 1 << 20
)

// ManagedLifecycleHost is the app-blind caller-transaction execution kernel.
// Controlplane prepares its coordination schema and target markers; this host
// only validates and attaches to that state, then stages bounded effects in an
// already-open Runtime transaction.
type ManagedLifecycleHost struct {
	db           *sql.DB
	catalog      managedLifecycleCatalog
	registration cp.ComponentKindRegistration
	lifecycle    cp.ComponentManagedLifecycleRegistration
	effects      map[cp.ComponentManagedEffectClass]ManagedLifecycleEffectAdapter
	ddlGate      chan struct{}
	ddlReady     bool
}

// NewManagedLifecycleHost validates one immutable lifecycle binding without
// creating schema, markers, or another metadata authority.
func NewManagedLifecycleHost(
	db *sql.DB,
	registration cp.ComponentKindRegistration,
	lifecycle cp.ComponentManagedLifecycleRegistration,
	effects []ManagedLifecycleEffectAdapter,
) (*ManagedLifecycleHost, error) {
	if db == nil {
		return nil, fmt.Errorf("managed lifecycle database is required")
	}
	if lifecycle.Kind() != registration.Contract.Kind || lifecycle.Kind() == "" {
		return nil, fmt.Errorf("managed lifecycle kind does not match registration")
	}
	if lifecycle.TransactionMode() != cp.ComponentManagedTransactionPessimisticReadCommitted {
		return nil, fmt.Errorf("managed lifecycle transaction mode %q is unsupported", lifecycle.TransactionMode())
	}
	if len(registration.Contract.WriteSet) == 0 {
		return nil, fmt.Errorf("managed lifecycle write set is required")
	}
	for _, surface := range registration.Contract.WriteSet {
		if err := validateComponentSQLIdentifier(surface.Table); err != nil {
			return nil, fmt.Errorf("managed lifecycle write set: %w", err)
		}
	}
	for _, surface := range lifecycle.ReadSet() {
		if err := validateComponentSQLIdentifier(surface.Table); err != nil {
			return nil, fmt.Errorf("managed lifecycle read set: %w", err)
		}
	}
	if err := ValidateManagedLifecycleEffectAdapters(effects); err != nil {
		return nil, err
	}
	available := make(map[cp.ComponentManagedEffectClass]ManagedLifecycleEffectAdapter, len(effects))
	for _, adapter := range effects {
		available[adapter.Class] = adapter
	}
	selected := make(map[cp.ComponentManagedEffectClass]ManagedLifecycleEffectAdapter)
	for _, class := range lifecycle.EffectClasses() {
		adapter, ok := available[class]
		if !ok {
			return nil, fmt.Errorf("managed lifecycle effect %q is not configured", class)
		}
		selected[class] = adapter
	}
	host := &ManagedLifecycleHost{
		db:           db,
		catalog:      sqlManagedLifecycleCatalog{exec: db},
		registration: registration,
		lifecycle:    lifecycle,
		effects:      selected,
		ddlGate:      make(chan struct{}, 1),
	}
	host.ddlGate <- struct{}{}
	return host, nil
}

type managedLifecycleReadView struct {
	delegate cp.ComponentManagedTransactionReadStore
}

var _ cp.ComponentManagedKeysetTransactionReadStore = managedLifecycleReadView{}

func (v managedLifecycleReadView) ReadRows(ctx context.Context, query cp.ComponentManagedRowQuery) ([]cp.ComponentRow, error) {
	return v.delegate.ReadRows(ctx, query)
}

func (v managedLifecycleReadView) ReadRowsPage(ctx context.Context, query cp.ComponentManagedKeysetRowQuery) ([]cp.ComponentRow, error) {
	return v.delegate.ReadRowsPage(ctx, query)
}

func (v managedLifecycleReadView) ReadRowsKeysetPage(ctx context.Context, query cp.ComponentManagedKeysetRowQuery) (cp.ComponentManagedKeysetRowPage, error) {
	delegate, ok := v.delegate.(cp.ComponentManagedKeysetTransactionReadStore)
	if !ok {
		return cp.ComponentManagedKeysetRowPage{}, fmt.Errorf("managed lifecycle keyset exhaustion is unavailable")
	}
	return delegate.ReadRowsKeysetPage(ctx, query)
}

func (h *ManagedLifecycleHost) validatePlanEffectAdapters(plan cp.ComponentManagedPlan) error {
	for _, requirement := range plan.Effects() {
		if _, ok := h.effects[requirement.Class]; !ok {
			return fmt.Errorf("managed lifecycle effect %q is not configured", requirement.Class)
		}
	}
	return nil
}

func validateManagedLifecycleHostPlan(plan cp.ComponentManagedPlan) error {
	if size := len(plan.ProjectionIdentity()); size > managedLifecycleMaximumProjectionIdentityBytes {
		return fmt.Errorf("managed lifecycle projection identity contains %d bytes; maximum is %d", size, managedLifecycleMaximumProjectionIdentityBytes)
	}
	return nil
}

func validateManagedLifecycleLockedEnvelope(ambient, locked cp.ComponentManagedPlan) error {
	if !reflect.DeepEqual(ambient.RequiredCapabilities(), locked.RequiredCapabilities()) {
		return fmt.Errorf("managed lifecycle locked plan changed required capabilities")
	}
	if !reflect.DeepEqual(ambient.Effects(), locked.Effects()) {
		return fmt.Errorf("managed lifecycle locked plan changed required effects")
	}
	if !bytes.Equal(ambient.ProjectionIdentity(), locked.ProjectionIdentity()) {
		return fmt.Errorf("managed lifecycle locked plan changed projection identity")
	}
	return nil
}

func validateManagedLifecycleStagedMutation(
	budget cp.ComponentManagedBudget,
	plan cp.ComponentManagedPlan,
	mutation cp.ComponentManagedMutationResult,
	receipt cp.ComponentManagedMutationReceipt,
) error {
	if err := mutation.Validate(); err != nil {
		return fmt.Errorf("managed lifecycle mutation result: %w", err)
	}
	usage := receipt.Usage
	if usage.RowsRead > usage.CurrentItems || usage.RowsLocked != usage.CurrentItems-usage.RowsRead {
		return fmt.Errorf("managed lifecycle current items must equal rows read plus rows locked")
	}
	if usage.RowsInserted > usage.DeltaRows || usage.RowsUpdated > usage.DeltaRows-usage.RowsInserted ||
		usage.RowsDeleted != usage.DeltaRows-usage.RowsInserted-usage.RowsUpdated {
		return fmt.Errorf("managed lifecycle delta rows must equal rows inserted, updated, and deleted")
	}
	if err := validateManagedLifecycleUsage(budget, usage); err != nil {
		return err
	}
	matchingEffects := 0
	for _, effect := range plan.Effects() {
		for _, trigger := range effect.Triggers {
			if trigger == mutation.Disposition {
				matchingEffects++
				break
			}
		}
	}
	if matchingEffects > budget.MaxEffectCalls {
		return fmt.Errorf("managed lifecycle required effect calls %d exceed budget %d", matchingEffects, budget.MaxEffectCalls)
	}
	if budget.MaxEvidenceWrites < 1 {
		return fmt.Errorf("managed lifecycle evidence-write budget is exhausted")
	}
	switch mutation.Disposition {
	case cp.ComponentManagedMutationNoChange:
		if usage.DeltaRows != 0 || usage.MutationStatements != 0 || matchingEffects != 0 {
			return fmt.Errorf("managed lifecycle mutation disposition %q contradicts actual semantic usage", mutation.Disposition)
		}
	case cp.ComponentManagedMutationDesiredProjectionStaged, cp.ComponentManagedMutationSafeProjectionStaged:
		if usage.DeltaRows == 0 && matchingEffects == 0 {
			return fmt.Errorf("managed lifecycle mutation disposition %q requires an actual row or effect change", mutation.Disposition)
		}
	}
	return nil
}

type managedLifecycleEffectReceipt struct {
	Class   cp.ComponentManagedEffectClass `json:"class"`
	Receipt []byte                         `json:"receipt"`
}

func (h *ManagedLifecycleHost) applyEffects(
	ctx context.Context,
	tx *sql.Tx,
	operationID []byte,
	attempt int,
	scope cp.ComponentManagedScope,
	mutation cp.ComponentManagedMutationResult,
	plan cp.ComponentManagedPlan,
) ([]managedLifecycleEffectReceipt, []cp.ComponentManagedEffectClass, error) {
	receipts := make([]managedLifecycleEffectReceipt, 0)
	classes := make([]cp.ComponentManagedEffectClass, 0)
	for _, requirement := range plan.Effects() {
		triggered := false
		for _, trigger := range requirement.Triggers {
			triggered = triggered || trigger == mutation.Disposition
		}
		if !triggered {
			continue
		}
		adapter, ok := h.effects[requirement.Class]
		if !ok {
			return nil, nil, fmt.Errorf("managed lifecycle effect %q is not configured", requirement.Class)
		}
		request := ManagedLifecycleEffectRequest{
			OperationID: append([]byte(nil), operationID...),
			Attempt:     attempt,
			Kind:        h.lifecycle.Kind(),
			Scope:       scope,
			Disposition: mutation.Disposition,
		}
		receipt, err := adapter.Apply(ctx, tx, request)
		if err != nil {
			return nil, nil, fmt.Errorf("apply managed lifecycle effect %q: %w", requirement.Class, err)
		}
		if len(receipt) == 0 || len(receipt) > managedLifecycleMaximumEffectReceiptBytes {
			return nil, nil, fmt.Errorf("managed lifecycle effect %q receipt must contain 1..%d bytes", requirement.Class, managedLifecycleMaximumEffectReceiptBytes)
		}
		receipts = append(receipts, managedLifecycleEffectReceipt{Class: requirement.Class, Receipt: append([]byte(nil), receipt...)})
		classes = append(classes, requirement.Class)
	}
	return receipts, classes, nil
}

func insertManagedLifecycleEvidence(
	ctx context.Context,
	tx *sql.Tx,
	operationID []byte,
	attempt int,
	kind cp.ComponentKind,
	scope cp.ComponentManagedScope,
	plan cp.ComponentManagedPlan,
	mutation cp.ComponentManagedMutationResult,
	receipts []managedLifecycleEffectReceipt,
	semanticMutationRows int,
) error {
	encoded, err := json.Marshal(receipts)
	if err != nil {
		return err
	}
	if len(encoded) > managedLifecycleMaximumEvidenceReceiptBytes {
		return fmt.Errorf("managed lifecycle encoded effect receipts contain %d bytes; maximum is %d", len(encoded), managedLifecycleMaximumEvidenceReceiptBytes)
	}
	projectionIdentityHash := sha256.Sum256(plan.ProjectionIdentity())
	var effectReceiptHash []byte
	if len(receipts) != 0 {
		hash := sha256.Sum256(encoded)
		effectReceiptHash = hash[:]
	}
	_, err = tx.ExecContext(ctx,
		"INSERT INTO `_System_ManagedComponentOperationEvidence` (`OperationId`, `Attempt`, `ComponentKind`, `Scope`, `PlanFingerprint`, `ProjectionIdentityHash`, `MutationDisposition`, `SemanticMutationRows`, `EffectReceiptHash`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
		operationID, attempt, string(kind), string(scope), plan.Fingerprint(), projectionIdentityHash[:],
		string(mutation.Disposition), semanticMutationRows, effectReceiptHash,
	)
	if err != nil {
		return fmt.Errorf("insert managed lifecycle evidence: %w", err)
	}
	return nil
}

func cloneManagedLifecycleProjection(projection cp.ComponentManagedProjection) cp.ComponentManagedProjection {
	return cp.ComponentManagedProjection{
		Identity: append([]byte(nil), projection.Identity...),
		Expected: append([]byte(nil), projection.Expected...),
		Live:     append([]byte(nil), projection.Live...),
	}
}
