package schemacatalog

import (
	"fmt"
	"strings"
	"sync"

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

// SystemTableFieldWriteProtection is the immutable write-protection fact
// projected from one canonical system-table column.
type SystemTableFieldWriteProtection struct {
	FieldName    string
	PhysicalName string
	Policy       string
}

type systemTableWriteProtectionInventory map[string]map[string]SystemTableFieldWriteProtection

var loadSystemTableWriteProtectionInventory = sync.OnceValues(func() (systemTableWriteProtectionInventory, error) {
	definitions, err := ManagedTableDefinitions()
	if err != nil {
		return systemTableWriteProtectionInventory{}, err
	}
	return systemTableWriteProtectionInventoryFromDefinitions(definitions)
})

// SystemTableFieldWriteProtectionFor resolves a canonical system-table
// write-protection fact by case-insensitive table and logical-or-physical field
// name. The lookup is a derived immutable projection of system_tables.json;
// that catalog remains the sole fact owner.
func SystemTableFieldWriteProtectionFor(tableName, fieldName string) (SystemTableFieldWriteProtection, bool, error) {
	fields, err := SystemTableFieldWriteProtectionsFor(tableName)
	if err != nil {
		return SystemTableFieldWriteProtection{}, false, err
	}
	fact, ok := fields[normalizeWriteProtectionLookupKey(fieldName)]
	return fact, ok, nil
}

// SystemTableFieldWriteProtectionsFor returns a detached alias-keyed projection
// of every protected field on one canonical system table. Keys are normalized
// logical and physical field names.
func SystemTableFieldWriteProtectionsFor(tableName string) (map[string]SystemTableFieldWriteProtection, error) {
	inventory, err := loadSystemTableWriteProtectionInventory()
	if err != nil {
		return nil, err
	}
	fields := inventory[normalizeWriteProtectionLookupKey(tableName)]
	if len(fields) == 0 {
		return nil, nil
	}
	out := make(map[string]SystemTableFieldWriteProtection, len(fields))
	for alias, fact := range fields {
		out[alias] = fact
	}
	return out, nil
}

func systemTableWriteProtectionInventoryFromDefinitions(definitions []schema.TableDefinition) (systemTableWriteProtectionInventory, error) {
	inventory := make(systemTableWriteProtectionInventory)
	for _, definition := range definitions {
		tableKey := normalizeWriteProtectionLookupKey(definition.TableName)
		if tableKey == "" {
			continue
		}
		fields := make(map[string]SystemTableFieldWriteProtection)
		for _, column := range definition.Columns {
			policy := strings.TrimSpace(column.WriteProtectionPolicy)
			if policy == "" {
				continue
			}
			canonicalName := strings.TrimSpace(column.Name)
			physicalName := strings.TrimSpace(column.PhysicalName)
			if physicalName == "" {
				physicalName = canonicalName
			}
			fact := SystemTableFieldWriteProtection{
				FieldName:    canonicalName,
				PhysicalName: physicalName,
				Policy:       policy,
			}
			for _, alias := range []string{canonicalName, physicalName} {
				aliasKey := normalizeWriteProtectionLookupKey(alias)
				if aliasKey == "" {
					continue
				}
				if existing, exists := fields[aliasKey]; exists &&
					(existing.FieldName != fact.FieldName ||
						existing.PhysicalName != fact.PhysicalName ||
						existing.Policy != fact.Policy) {
					return nil, fmt.Errorf(
						"system table %s has conflicting write-protection declarations for field alias %s",
						definition.TableName,
						alias,
					)
				}
				fields[aliasKey] = fact
			}
		}
		if len(fields) > 0 {
			inventory[tableKey] = fields
		}
	}
	return inventory, nil
}

func (inventory systemTableWriteProtectionInventory) lookup(tableName, fieldName string) (SystemTableFieldWriteProtection, bool) {
	fields := inventory[normalizeWriteProtectionLookupKey(tableName)]
	if len(fields) == 0 {
		return SystemTableFieldWriteProtection{}, false
	}
	fact, ok := fields[normalizeWriteProtectionLookupKey(fieldName)]
	return fact, ok
}

func normalizeWriteProtectionLookupKey(value string) string {
	return strings.ToLower(strings.TrimSpace(value))
}
