package catalogapp

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"sort"

	"github.com/pingcap-inc/nexuscrm-contract/pkg/appauthority"
	layoutbundlekeyv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/layoutbundlekeyv1"
	"github.com/pingcap-inc/nexuscrm-controlplane/internal/catalogadmissionfence"
)

// ObservedAppNamespaceDeclaration is one namespace declared by an App identity
// or one of its registered immutable releases. It is transient stopped-state
// preflight inventory, not a namespace-owner claim, an issuer capability, or
// authorization to map a ProfileKey or RoleKey.
type ObservedAppNamespaceDeclaration struct {
	NamespaceID string
	AppID       string
}

type registeredReleaseKey struct {
	appID  string
	digest string
}

type storedRegisteredRelease struct {
	key                  registeredReleaseKey
	publisherID          string
	semanticVersion      string
	metadataBundleDigest string
	canonicalManifest    []byte
	canonicalBundle      []byte
}

// InspectRegisteredNamespaceDeclarations opens a source-owned transaction,
// locks the existing App source-serialization fence, and verifies the complete
// namespace declaration projection for every registered immutable AppRelease.
// It inventories declarations, not a release lifecycle proof. It always rolls
// back before returning and cannot mutate or adopt namespace state. A later
// migration must independently re-inspect and revalidate its source facts in
// its own stopped-phase transaction before performing any mutation.
func (a *Authority) InspectRegisteredNamespaceDeclarations(
	ctx context.Context,
) (declarations []ObservedAppNamespaceDeclaration, returnErr error) {
	if a == nil || a.db == nil || a.identity == nil {
		return nil, ErrConflict
	}

	tx, err := a.db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer func() {
		rollbackErr := tx.Rollback()
		if rollbackErr != nil {
			declarations = nil
			returnErr = errors.Join(returnErr, rollbackErr)
		}
	}()
	if _, err := catalogadmissionfence.LockExclusive(ctx, tx); err != nil {
		return nil, fmt.Errorf(
			"%w: App source-serialization fence: %w",
			ErrCorrupt,
			err,
		)
	}

	declarations, err = inspectNamespaceDeclarationLineage(ctx, tx)
	if err != nil {
		return nil, err
	}
	return declarations, nil
}

func inspectNamespaceDeclarationLineage(
	ctx context.Context,
	tx *sql.Tx,
) ([]ObservedAppNamespaceDeclaration, error) {
	identities, err := readRegisteredAppIdentities(ctx, tx)
	if err != nil {
		return nil, err
	}
	releases, err := readRegisteredReleases(ctx, tx)
	if err != nil {
		return nil, err
	}
	components, err := readRegisteredReleaseComponents(ctx, tx)
	if err != nil {
		return nil, err
	}
	projections, err := readRegisteredReleaseNamespaceProjections(ctx, tx)
	if err != nil {
		return nil, err
	}

	for key := range components {
		if _, found := releases[key]; !found {
			return nil, fmt.Errorf(
				"%w: AppReleaseComponent has no parent AppRelease",
				ErrCorrupt,
			)
		}
	}

	observed := make(map[string]string, len(identities))
	add := func(namespaceID, appID string) error {
		if prior, found := observed[namespaceID]; found {
			if prior != appID {
				return fmt.Errorf(
					"%w: namespace %q is declared by Apps %q and %q",
					ErrConflict,
					namespaceID,
					prior,
					appID,
				)
			}
			return nil
		}
		observed[namespaceID] = appID
		return nil
	}

	for appID := range identities {
		if err := add(appID, appID); err != nil {
			return nil, err
		}
	}

	keys := make([]registeredReleaseKey, 0, len(releases))
	for key := range releases {
		keys = append(keys, key)
	}
	sort.Slice(keys, func(i, j int) bool {
		if keys[i].appID != keys[j].appID {
			return keys[i].appID < keys[j].appID
		}
		return keys[i].digest < keys[j].digest
	})
	for _, key := range keys {
		release := releases[key]
		if _, found := identities[release.key.appID]; !found {
			return nil, fmt.Errorf(
				"%w: AppRelease %q has no AppIdentity",
				ErrCorrupt,
				release.key.appID,
			)
		}
		componentBlobs := components[release.key]
		if componentBlobs == nil {
			componentBlobs = []appauthority.ComponentBlob{}
		}
		verified, err := verifyStoredRegisteredRelease(release, componentBlobs)
		if err != nil {
			return nil, err
		}

		expected := make(map[string]struct{}, len(verified.Manifest.OwnedNamespaces))
		for _, namespaceID := range verified.Manifest.OwnedNamespaces {
			if err := validateAppDeclaredNamespace(namespaceID); err != nil {
				return nil, err
			}
			expected[namespaceID] = struct{}{}
		}
		actual := projections[release.key]
		if len(actual) != len(expected) {
			return nil, fmt.Errorf(
				"%w: AppRelease namespace projection differs from canonical manifest",
				ErrCorrupt,
			)
		}
		for namespaceID := range expected {
			if _, found := actual[namespaceID]; !found {
				return nil, fmt.Errorf(
					"%w: AppRelease namespace %q is missing from its projection",
					ErrCorrupt,
					namespaceID,
				)
			}
			if err := add(namespaceID, release.key.appID); err != nil {
				return nil, err
			}
		}
		delete(projections, release.key)
	}
	if len(projections) != 0 {
		return nil, fmt.Errorf(
			"%w: AppReleaseNamespace has no parent AppRelease",
			ErrCorrupt,
		)
	}

	namespaceIDs := make([]string, 0, len(observed))
	for namespaceID := range observed {
		namespaceIDs = append(namespaceIDs, namespaceID)
	}
	sort.Strings(namespaceIDs)
	result := make([]ObservedAppNamespaceDeclaration, 0, len(namespaceIDs))
	for _, namespaceID := range namespaceIDs {
		result = append(result, ObservedAppNamespaceDeclaration{
			NamespaceID: namespaceID,
			AppID:       observed[namespaceID],
		})
	}
	return result, nil
}

func readRegisteredAppIdentities(
	ctx context.Context,
	tx *sql.Tx,
) (map[string]struct{}, error) {
	rows, err := tx.QueryContext(ctx, `SELECT AppId
FROM _System_CatalogAppIdentity
ORDER BY AppId
FOR UPDATE`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	identities := make(map[string]struct{})
	for rows.Next() {
		var appID string
		if err := rows.Scan(&appID); err != nil {
			return nil, err
		}
		if err := appauthority.ValidateAppID(appID); err != nil {
			return nil, fmt.Errorf("%w: AppIdentity AppId: %v", ErrCorrupt, err)
		}
		if appID == layoutbundlekeyv1.SystemSlotOwnerNamespace {
			return nil, fmt.Errorf(
				"%w: AppIdentity cannot use the reserved system namespace",
				ErrCorrupt,
			)
		}
		if _, found := identities[appID]; found {
			return nil, fmt.Errorf("%w: duplicate AppIdentity AppId", ErrCorrupt)
		}
		identities[appID] = struct{}{}
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return identities, nil
}

func readRegisteredReleases(
	ctx context.Context,
	tx *sql.Tx,
) (map[registeredReleaseKey]storedRegisteredRelease, error) {
	rows, err := tx.QueryContext(ctx, `SELECT AppId, AppReleaseDigest, PublisherId,
SemanticVersion, MetadataBundleDigest, CanonicalManifest, CanonicalBundle
FROM _System_CatalogAppRelease
ORDER BY AppId, AppReleaseDigest
FOR UPDATE`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	releases := make(map[registeredReleaseKey]storedRegisteredRelease)
	for rows.Next() {
		var release storedRegisteredRelease
		if err := rows.Scan(
			&release.key.appID,
			&release.key.digest,
			&release.publisherID,
			&release.semanticVersion,
			&release.metadataBundleDigest,
			&release.canonicalManifest,
			&release.canonicalBundle,
		); err != nil {
			return nil, err
		}
		if err := appauthority.ValidateAppID(release.key.appID); err != nil {
			return nil, fmt.Errorf("%w: AppRelease AppId: %v", ErrCorrupt, err)
		}
		if err := requireDigest("AppRelease digest", release.key.digest); err != nil {
			return nil, fmt.Errorf("%w: %v", ErrCorrupt, err)
		}
		if _, found := releases[release.key]; found {
			return nil, fmt.Errorf("%w: duplicate AppRelease", ErrCorrupt)
		}
		releases[release.key] = release
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return releases, nil
}

func readRegisteredReleaseComponents(
	ctx context.Context,
	tx *sql.Tx,
) (map[registeredReleaseKey][]appauthority.ComponentBlob, error) {
	rows, err := tx.QueryContext(ctx, `SELECT AppId, AppReleaseDigest, ComponentId,
CanonicalBehavior
FROM _System_CatalogAppReleaseComponent
ORDER BY AppId, AppReleaseDigest, ComponentId
FOR UPDATE`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	components := make(map[registeredReleaseKey][]appauthority.ComponentBlob)
	seen := make(map[registeredReleaseKey]map[string]struct{})
	for rows.Next() {
		var key registeredReleaseKey
		var component appauthority.ComponentBlob
		if err := rows.Scan(
			&key.appID,
			&key.digest,
			&component.ComponentID,
			&component.CanonicalBehavior,
		); err != nil {
			return nil, err
		}
		if err := appauthority.ValidateAppID(key.appID); err != nil {
			return nil, fmt.Errorf("%w: AppReleaseComponent AppId: %v", ErrCorrupt, err)
		}
		if err := requireDigest("AppReleaseComponent release digest", key.digest); err != nil {
			return nil, fmt.Errorf("%w: %v", ErrCorrupt, err)
		}
		if seen[key] == nil {
			seen[key] = make(map[string]struct{})
		}
		if _, found := seen[key][component.ComponentID]; found {
			return nil, fmt.Errorf("%w: duplicate AppReleaseComponent", ErrCorrupt)
		}
		seen[key][component.ComponentID] = struct{}{}
		components[key] = append(components[key], component)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return components, nil
}

func readRegisteredReleaseNamespaceProjections(
	ctx context.Context,
	tx *sql.Tx,
) (map[registeredReleaseKey]map[string]struct{}, error) {
	rows, err := tx.QueryContext(ctx, `SELECT AppId, AppReleaseDigest, NamespaceId
FROM _System_CatalogAppReleaseNamespace
ORDER BY AppId, AppReleaseDigest, NamespaceId
FOR UPDATE`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	projections := make(map[registeredReleaseKey]map[string]struct{})
	for rows.Next() {
		var key registeredReleaseKey
		var namespaceID string
		if err := rows.Scan(&key.appID, &key.digest, &namespaceID); err != nil {
			return nil, err
		}
		if err := appauthority.ValidateAppID(key.appID); err != nil {
			return nil, fmt.Errorf("%w: AppReleaseNamespace AppId: %v", ErrCorrupt, err)
		}
		if err := requireDigest("AppReleaseNamespace release digest", key.digest); err != nil {
			return nil, fmt.Errorf("%w: %v", ErrCorrupt, err)
		}
		if err := validateAppDeclaredNamespace(namespaceID); err != nil {
			return nil, err
		}
		if projections[key] == nil {
			projections[key] = make(map[string]struct{})
		}
		if _, found := projections[key][namespaceID]; found {
			return nil, fmt.Errorf("%w: duplicate AppReleaseNamespace", ErrCorrupt)
		}
		projections[key][namespaceID] = struct{}{}
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return projections, nil
}

func validateAppDeclaredNamespace(namespaceID string) error {
	if err := appauthority.ValidateNamespaceID(namespaceID); err != nil {
		return fmt.Errorf("%w: AppReleaseNamespace NamespaceId: %v", ErrCorrupt, err)
	}
	if namespaceID == layoutbundlekeyv1.SystemSlotOwnerNamespace {
		return fmt.Errorf(
			"%w: AppReleaseNamespace cannot use the reserved system namespace",
			ErrCorrupt,
		)
	}
	return nil
}

func verifyStoredRegisteredRelease(
	release storedRegisteredRelease,
	components []appauthority.ComponentBlob,
) (appauthority.VerifiedPackage, error) {
	manifest, err := appauthority.ParseAppManifest(release.canonicalManifest)
	if err != nil {
		return appauthority.VerifiedPackage{}, fmt.Errorf(
			"%w: AppRelease canonical manifest: %v",
			ErrCorrupt,
			err,
		)
	}
	bundle, err := appauthority.ParseMetadataBundle(release.canonicalBundle, manifest)
	if err != nil {
		return appauthority.VerifiedPackage{}, fmt.Errorf(
			"%w: AppRelease canonical bundle: %v",
			ErrCorrupt,
			err,
		)
	}
	componentSet, err := verifyReleaseComponentSet(
		appauthority.Package{
			Manifest:       manifest,
			Bundle:         bundle,
			ComponentBlobs: components,
		},
	)
	if err != nil {
		return appauthority.VerifiedPackage{}, fmt.Errorf(
			"%w: AppRelease canonical package: %v",
			ErrCorrupt,
			err,
		)
	}
	verified := componentSet.packageFacts
	if verified.AppReleaseDigest != release.key.digest ||
		verified.Manifest.AppID != release.key.appID ||
		verified.Manifest.PublisherID != release.publisherID ||
		verified.Manifest.SemanticVersion != release.semanticVersion ||
		verified.Manifest.MetadataBundleDigest != release.metadataBundleDigest {
		return appauthority.VerifiedPackage{}, fmt.Errorf(
			"%w: AppRelease stored tuple differs from canonical package",
			ErrCorrupt,
		)
	}
	return verified, nil
}
