package schemacatalog

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"regexp"
	"sort"
	"strings"

	"github.com/nexuscrm/kernel/pkg/schema"
)

const PermissionStateMutationInventorySchema = "nexuscrm.permission-state-mutation-inventory.v1"

var permissionStateMutationSurfaceIDPattern = regexp.MustCompile(
	`^platform\.permission-state\.[a-z0-9]+(?:-[a-z0-9]+)*\.v[1-9][0-9]*$`,
)

// PermissionStateMutationSurface is one platform-owned semantic row
// projection. Generic and direct writers use the same stable SurfaceID and
// compare only SemanticFields when deciding whether an update changed
// permission meaning.
type PermissionStateMutationSurface struct {
	SurfaceID      string   `json:"surfaceId"`
	TableName      string   `json:"tableName"`
	SemanticFields []string `json:"semanticFields"`
}

// PermissionStateMutationInventory is the validated, canonical projection of
// permissionStateMutation declarations in the embedded managed-table catalog.
// The embedded catalog remains authoritative; this generated value is the
// executable and auditable projection consumed by later writer registration.
type PermissionStateMutationInventory struct {
	surfaces              []PermissionStateMutationSurface
	deleteSurfacesByTable map[string][]PermissionStateMutationSurface
	canonical             []byte
	digest                string
}

// ManagedPermissionStateMutationInventory loads only the platform's embedded
// managed-table catalog. L1/L2 object metadata cannot add authority-sensitive
// mutation surfaces through this path.
func ManagedPermissionStateMutationInventory() (PermissionStateMutationInventory, error) {
	definitions, err := ManagedTableDefinitions()
	if err != nil {
		return PermissionStateMutationInventory{}, err
	}
	return permissionStateMutationInventoryFromCatalog(systemTablesJSON, definitions)
}

// SchemaVersion returns the canonical inventory wire schema.
func (inventory PermissionStateMutationInventory) SchemaVersion() string {
	return PermissionStateMutationInventorySchema
}

// Surfaces returns a deep copy in canonical SurfaceID order.
func (inventory PermissionStateMutationInventory) Surfaces() []PermissionStateMutationSurface {
	return clonePermissionStateMutationSurfaces(inventory.surfaces)
}

// SurfaceForTable returns a deep copy of a managed table's declaration.
func (inventory PermissionStateMutationInventory) SurfaceForTable(
	tableName string,
) (PermissionStateMutationSurface, bool) {
	for _, surface := range inventory.surfaces {
		if strings.EqualFold(surface.TableName, tableName) {
			return clonePermissionStateMutationSurface(surface), true
		}
	}
	return PermissionStateMutationSurface{}, false
}

// SurfacesAffectedByDelete returns the target table's semantic surface, when
// present, plus every semantic surface reachable through a transitive
// catalog-declared ON DELETE CASCADE edge. A non-surface parent such as User
// can therefore still have Permission State delete effects.
func (inventory PermissionStateMutationInventory) SurfacesAffectedByDelete(
	tableName string,
) []PermissionStateMutationSurface {
	return clonePermissionStateMutationSurfaces(
		inventory.deleteSurfacesByTable[strings.ToLower(strings.TrimSpace(tableName))],
	)
}

// CanonicalJSON returns an immutable copy of the digest input.
func (inventory PermissionStateMutationInventory) CanonicalJSON() []byte {
	return append([]byte(nil), inventory.canonical...)
}

// Digest returns the sha256-qualified digest of CanonicalJSON.
func (inventory PermissionStateMutationInventory) Digest() string {
	return inventory.digest
}

type permissionStateMutationCatalogEntry struct {
	TableName               string                                     `json:"tableName"`
	PermissionStateMutation *permissionStateMutationCatalogDeclaration `json:"permissionStateMutation,omitempty"`
	Columns                 []permissionStateMutationCatalogForeignKey `json:"columns"`
	ForeignKeys             []permissionStateMutationCatalogForeignKey `json:"foreignKeys"`
}

type permissionStateMutationCatalogDeclaration struct {
	SurfaceID      string   `json:"surfaceId"`
	SemanticFields []string `json:"semanticFields"`
}

type permissionStateMutationCatalogForeignKey struct {
	References string `json:"references"`
	OnDelete   string `json:"onDelete"`
}

type canonicalPermissionStateMutationInventory struct {
	SchemaVersion string                           `json:"schemaVersion"`
	Surfaces      []PermissionStateMutationSurface `json:"surfaces"`
}

func permissionStateMutationInventoryFromCatalog(
	raw []byte,
	definitions []schema.TableDefinition,
) (PermissionStateMutationInventory, error) {
	var entries []permissionStateMutationCatalogEntry
	if err := json.Unmarshal(raw, &entries); err != nil {
		return PermissionStateMutationInventory{}, fmt.Errorf("permission state mutation inventory: parse managed catalog: %w", err)
	}

	definitionsByName := make(map[string]schema.TableDefinition, len(definitions))
	for _, definition := range definitions {
		tableName := strings.TrimSpace(definition.TableName)
		key := strings.ToLower(tableName)
		if tableName == "" || tableName != definition.TableName {
			return PermissionStateMutationInventory{}, fmt.Errorf(
				"permission state mutation inventory: managed table name %q is invalid",
				definition.TableName,
			)
		}
		if _, duplicate := definitionsByName[key]; duplicate {
			return PermissionStateMutationInventory{}, fmt.Errorf("permission state mutation inventory: duplicate managed table %s", definition.TableName)
		}
		definitionsByName[key] = definition
	}

	surfaces := make([]PermissionStateMutationSurface, 0)
	surfaceIDs := make(map[string]string)
	tables := make(map[string]struct{})
	for _, entry := range entries {
		if entry.PermissionStateMutation == nil {
			continue
		}
		tableName := strings.TrimSpace(entry.TableName)
		definition, ok := definitionsByName[strings.ToLower(tableName)]
		if tableName != entry.TableName || !ok || definition.TableName != tableName {
			return PermissionStateMutationInventory{}, fmt.Errorf(
				"permission state mutation inventory: declaration table %q is not an exact managed table",
				entry.TableName,
			)
		}
		if strings.EqualFold(tableName, "_System_PermissionStateVersion") {
			return PermissionStateMutationInventory{}, errors.New(
				"permission state mutation inventory: protocol singleton cannot be a semantic mutation surface",
			)
		}
		tableKey := strings.ToLower(tableName)
		if _, duplicate := tables[tableKey]; duplicate {
			return PermissionStateMutationInventory{}, fmt.Errorf(
				"permission state mutation inventory: duplicate declaration for table %s",
				tableName,
			)
		}
		tables[tableKey] = struct{}{}

		declaration := entry.PermissionStateMutation
		surfaceID := strings.TrimSpace(declaration.SurfaceID)
		if surfaceID != declaration.SurfaceID || !permissionStateMutationSurfaceIDPattern.MatchString(surfaceID) {
			return PermissionStateMutationInventory{}, fmt.Errorf(
				"permission state mutation inventory: table %s has invalid surface ID %q",
				tableName,
				declaration.SurfaceID,
			)
		}
		if previous, duplicate := surfaceIDs[surfaceID]; duplicate {
			return PermissionStateMutationInventory{}, fmt.Errorf(
				"permission state mutation inventory: surface ID %s is shared by %s and %s",
				surfaceID,
				previous,
				tableName,
			)
		}
		surfaceIDs[surfaceID] = tableName

		fields, err := validatePermissionStateSemanticFields(definition, declaration.SemanticFields)
		if err != nil {
			return PermissionStateMutationInventory{}, fmt.Errorf(
				"permission state mutation inventory: table %s: %w",
				tableName,
				err,
			)
		}
		surfaces = append(surfaces, PermissionStateMutationSurface{
			SurfaceID:      surfaceID,
			TableName:      tableName,
			SemanticFields: fields,
		})
	}
	if len(surfaces) == 0 {
		return PermissionStateMutationInventory{}, errors.New(
			"permission state mutation inventory: embedded catalog declares no semantic surfaces",
		)
	}

	sort.Slice(surfaces, func(i, j int) bool {
		if surfaces[i].SurfaceID == surfaces[j].SurfaceID {
			return surfaces[i].TableName < surfaces[j].TableName
		}
		return surfaces[i].SurfaceID < surfaces[j].SurfaceID
	})
	canonical, err := json.Marshal(canonicalPermissionStateMutationInventory{
		SchemaVersion: PermissionStateMutationInventorySchema,
		Surfaces:      surfaces,
	})
	if err != nil {
		return PermissionStateMutationInventory{}, fmt.Errorf("permission state mutation inventory: encode canonical projection: %w", err)
	}
	sum := sha256.Sum256(canonical)
	deleteSurfacesByTable := permissionStateDeleteSurfacesByTable(entries, surfaces)
	return PermissionStateMutationInventory{
		surfaces:              clonePermissionStateMutationSurfaces(surfaces),
		deleteSurfacesByTable: clonePermissionStateDeleteSurfacesByTable(deleteSurfacesByTable),
		canonical:             append([]byte(nil), canonical...),
		digest:                "sha256:" + hex.EncodeToString(sum[:]),
	}, nil
}

func permissionStateDeleteSurfacesByTable(
	entries []permissionStateMutationCatalogEntry,
	surfaces []PermissionStateMutationSurface,
) map[string][]PermissionStateMutationSurface {
	cascadeChildren := make(map[string][]string)
	for _, entry := range entries {
		for _, foreignKey := range entry.ForeignKeys {
			addPermissionStateCascadeChild(cascadeChildren, foreignKey, entry.TableName)
		}
		for _, column := range entry.Columns {
			addPermissionStateCascadeChild(cascadeChildren, column, entry.TableName)
		}
	}
	surfacesByTable := make(map[string]PermissionStateMutationSurface, len(surfaces))
	for _, surface := range surfaces {
		surfacesByTable[strings.ToLower(surface.TableName)] = surface
	}
	result := make(map[string][]PermissionStateMutationSurface)
	for _, entry := range entries {
		pending := []string{entry.TableName}
		visited := make(map[string]bool)
		affected := make([]PermissionStateMutationSurface, 0)
		for len(pending) > 0 {
			tableName := pending[0]
			pending = pending[1:]
			key := strings.ToLower(tableName)
			if visited[key] {
				continue
			}
			visited[key] = true
			if surface, ok := surfacesByTable[key]; ok {
				affected = append(affected, surface)
			}
			pending = append(pending, cascadeChildren[key]...)
		}
		if len(affected) == 0 {
			continue
		}
		sort.Slice(affected, func(i, j int) bool {
			return affected[i].SurfaceID < affected[j].SurfaceID
		})
		result[strings.ToLower(entry.TableName)] = affected
	}
	return result
}

func addPermissionStateCascadeChild(
	children map[string][]string,
	foreignKey permissionStateMutationCatalogForeignKey,
	child string,
) {
	if !strings.EqualFold(strings.TrimSpace(foreignKey.OnDelete), "CASCADE") {
		return
	}
	parent, _, _ := strings.Cut(strings.TrimSpace(foreignKey.References), "(")
	parent = strings.TrimSpace(parent)
	if parent == "" {
		return
	}
	key := strings.ToLower(parent)
	children[key] = append(children[key], child)
}

func clonePermissionStateDeleteSurfacesByTable(
	input map[string][]PermissionStateMutationSurface,
) map[string][]PermissionStateMutationSurface {
	output := make(map[string][]PermissionStateMutationSurface, len(input))
	for tableName, surfaces := range input {
		output[tableName] = clonePermissionStateMutationSurfaces(surfaces)
	}
	return output
}

func validatePermissionStateSemanticFields(
	definition schema.TableDefinition,
	declared []string,
) ([]string, error) {
	if len(declared) == 0 {
		return nil, errors.New("semantic fields are empty")
	}
	columns := make(map[string]string, len(definition.Columns))
	for _, column := range definition.Columns {
		columns[strings.ToLower(column.Name)] = column.Name
	}
	fields := make([]string, 0, len(declared))
	seen := make(map[string]struct{}, len(declared))
	for _, field := range declared {
		fieldName := strings.TrimSpace(field)
		key := strings.ToLower(fieldName)
		exact, ok := columns[key]
		if fieldName == "" || fieldName != field || !ok || exact != fieldName {
			return nil, fmt.Errorf("semantic field %q is not an exact managed column", field)
		}
		if key == "createddate" || key == "lastmodifieddate" {
			return nil, fmt.Errorf("audit field %s cannot define permission semantics", fieldName)
		}
		if _, duplicate := seen[key]; duplicate {
			return nil, fmt.Errorf("semantic field %s is duplicated", fieldName)
		}
		seen[key] = struct{}{}
		fields = append(fields, fieldName)
	}
	sort.Strings(fields)
	return fields, nil
}

func clonePermissionStateMutationSurfaces(
	input []PermissionStateMutationSurface,
) []PermissionStateMutationSurface {
	output := make([]PermissionStateMutationSurface, len(input))
	for index, surface := range input {
		output[index] = clonePermissionStateMutationSurface(surface)
	}
	return output
}

func clonePermissionStateMutationSurface(
	surface PermissionStateMutationSurface,
) PermissionStateMutationSurface {
	surface.SemanticFields = append([]string(nil), surface.SemanticFields...)
	return surface
}
