package models

import (
	"encoding/json"
	"fmt"
	"strings"
)

// ObjectReadSourceConfig declares the metadata-owned physical base read source
// for one logical object. Later runtime consumers must route through
// ResolveReadSourceTable instead of branching on object name.
type ObjectReadSourceConfig struct {
	Kind          string `json:"kind,omitempty"`
	PhysicalTable string `json:"physicalTable,omitempty"`
}

// NormalizeObjectReadSourceConfig trims and validates one readSource declaration.
// Nil remains nil; malformed declarations fail closed.
func NormalizeObjectReadSourceConfig(cfg *ObjectReadSourceConfig) (*ObjectReadSourceConfig, error) {
	if cfg == nil {
		return nil, nil
	}
	normalized := &ObjectReadSourceConfig{
		Kind:          strings.TrimSpace(cfg.Kind),
		PhysicalTable: strings.TrimSpace(cfg.PhysicalTable),
	}
	if normalized.Kind == "" {
		return nil, fmt.Errorf("readSource.kind is required")
	}
	switch normalized.Kind {
	case "physical_table":
		if normalized.PhysicalTable == "" {
			return nil, fmt.Errorf("readSource.physicalTable is required for kind=physical_table")
		}
		if !isSafeReadSourceIdentifier(normalized.PhysicalTable) {
			return nil, fmt.Errorf("readSource.physicalTable %q is not a safe identifier", normalized.PhysicalTable)
		}
		return normalized, nil
	default:
		return nil, fmt.Errorf("readSource.kind %q is unsupported", normalized.Kind)
	}
}

func CanonicalObjectReadSourceJSON(cfg *ObjectReadSourceConfig) (string, error) {
	normalized, err := NormalizeObjectReadSourceConfig(cfg)
	if err != nil || normalized == nil {
		return "", err
	}
	raw, err := json.Marshal(normalized)
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(raw)), nil
}

// ResolveReadSourceTable returns the physical base read table for the logical
// object. Missing declaration defaults to the logical API name. Malformed or
// unsupported declarations fail closed.
func (o *ObjectMetadata) ResolveReadSourceTable() (string, error) {
	if o == nil {
		return "", fmt.Errorf("object metadata is nil")
	}
	normalized, err := NormalizeObjectReadSourceConfig(o.ReadSource)
	if err != nil {
		return "", fmt.Errorf("%s %w", o.APIName, err)
	}
	if normalized == nil {
		return o.APIName, nil
	}
	return normalized.PhysicalTable, nil
}

func isSafeReadSourceIdentifier(name string) bool {
	if name == "" {
		return false
	}
	for _, c := range name {
		if !((c >= 'a' && c <= 'z') ||
			(c >= 'A' && c <= 'Z') ||
			(c >= '0' && c <= '9') ||
			c == '_') {
			return false
		}
	}
	return true
}
