package records

import (
	"encoding/json"
	"strings"
	"testing"

	"github.com/nexuscrm/kernel/pkg/schemacatalog"
	infraPersistence "github.com/nexuscrm/persistence"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/fieldprovenance"
)

// This file reconstructs, for records test setup only, the field-provenance
// fixture that previously came from
// bootstrap.InitFieldProvenanceFromStandardObjectSpecs(
//   bootstrap.DefaultPlatformStandardFields(), payload...).
//
// records is a sibling feature package and cannot import
// backend/internal/bootstrap (nor backend/internal/controlplane/packspec).
// The fixture is rebuilt from the already-landed sibling homes:
//
//   - system-tables manifest  -> github.com/nexuscrm/kernel/pkg/schemacatalog
//                                (SystemTablesJSON — the exact bytes bootstrap
//                                re-exposes; bootstrap.systemTablesJSON ==
//                                kernelschemacatalog.SystemTablesJSON()).
//   - standard-object spec     -> the caller-supplied test payload(s).
//   - tenant field manifest    -> empty. packspec leaves the tenant overlay
//                                manifest empty unless an operator explicitly
//                                registers one (RegisterTenantFieldManifest);
//                                no records test registers an overlay, so the
//                                old bootstrap path also saw an empty tenant
//                                slice here. Reproduced faithfully as nil.
//   - platform standard fields -> persistence.GetCustomObjectSystemColumns()
//                                + constants.FieldName (an exact replica of
//                                bootstrap.DefaultPlatformStandardFields()).
//
// The parse + classifier-build logic mirrors bootstrap's
// BuildFieldProvenanceClassifierFromStandardObjectSpecs /
// fieldprovenance_init.go field-decl normalization so the installed default
// classifier is behaviorally identical to the bootstrap fixture.

// initRecordsFieldProvenanceFixture builds the field-provenance default
// classifier from the kernel system-tables manifest plus the supplied
// standard-object spec payload(s) and installs it as the package default.
// It is the records-local, bootstrap-free replacement for
// bootstrap.InitFieldProvenanceFromStandardObjectSpecs(
//
//	bootstrap.DefaultPlatformStandardFields(), objectSpecPayloads...).
func initRecordsFieldProvenanceFixture(t *testing.T, objectSpecPayloads ...[]byte) {
	t.Helper()

	sysDecls, err := parseFPSystemTables(schemacatalog.SystemTablesJSON())
	if err != nil {
		t.Fatalf("init field provenance: parse system tables: %v", err)
	}

	var stdDecls []fieldprovenance.ObjectDeclaration
	for _, payload := range objectSpecPayloads {
		decls, err := parseFPSpecObjects(payload)
		if err != nil {
			t.Fatalf("init field provenance: parse standard object spec payload: %v", err)
		}
		stdDecls = append(stdDecls, decls...)
	}

	// Tenant overlay manifest is empty by default (no test registers one),
	// matching bootstrap's packspec.TenantFieldManifestJSON() during tests.
	classifier := fieldprovenance.BuildClassifier(
		sysDecls,
		stdDecls,
		nil,
		recordsDefaultPlatformStandardFields(),
	)
	fieldprovenance.SetDefault(classifier)
}

// recordsDefaultPlatformStandardFields is a byte-for-byte replica of
// bootstrap.DefaultPlatformStandardFields(): every custom-object system
// column name plus the platform Name fallback field. Any field name in this
// list is classified FieldSourceManaged regardless of the owning object.
func recordsDefaultPlatformStandardFields() []string {
	cols := infraPersistence.GetCustomObjectSystemColumns()
	out := make([]string, 0, len(cols)+1)
	for _, col := range cols {
		if col.Name == "" {
			continue
		}
		out = append(out, col.Name)
	}
	out = append(out, constants.FieldName)
	return out
}

// fpFieldDeclRaw is the common raw JSON shape for a field entry across the
// system-tables manifest ("name") and the standard-object spec ("apiName").
// Mirrors bootstrap.fpFieldDeclRaw.
type fpFieldDeclRaw struct {
	Name                  string   `json:"name,omitempty"`
	APIName               string   `json:"apiName,omitempty"`
	IsCompound            bool     `json:"isCompound,omitempty"`
	ComposedFrom          []string `json:"composedFrom,omitempty"`
	CompoundFieldName     string   `json:"compoundFieldName,omitempty"`
	CompoundComponentRole string   `json:"compoundComponentRole,omitempty"`
}

func (r fpFieldDeclRaw) apiName() string {
	if r.APIName != "" {
		return r.APIName
	}
	return r.Name
}

type fpSystemTableEntry struct {
	TableName string           `json:"tableName"`
	Columns   []fpFieldDeclRaw `json:"columns"`
}

type fpSpecObjectEntry struct {
	APIName string           `json:"apiName"`
	Fields  []fpFieldDeclRaw `json:"fields"`
}

// parseFPSystemTables mirrors bootstrap.parseFieldProvenanceSystemTables.
func parseFPSystemTables(data []byte) ([]fieldprovenance.ObjectDeclaration, error) {
	var raw []fpSystemTableEntry
	if err := json.Unmarshal(data, &raw); err != nil {
		return nil, err
	}
	out := make([]fieldprovenance.ObjectDeclaration, 0, len(raw))
	for _, obj := range raw {
		if obj.TableName == "" {
			continue
		}
		out = append(out, fieldprovenance.ObjectDeclaration{
			APIName: obj.TableName,
			Fields:  fpFieldDeclsFromRaw(obj.Columns),
		})
	}
	return out, nil
}

// parseFPSpecObjects mirrors bootstrap.parseFieldProvenanceSpecObjects.
func parseFPSpecObjects(data []byte) ([]fieldprovenance.ObjectDeclaration, error) {
	var raw []fpSpecObjectEntry
	if err := json.Unmarshal(data, &raw); err != nil {
		return nil, err
	}
	out := make([]fieldprovenance.ObjectDeclaration, 0, len(raw))
	for _, obj := range raw {
		if obj.APIName == "" {
			continue
		}
		out = append(out, fieldprovenance.ObjectDeclaration{
			APIName: obj.APIName,
			Fields:  fpFieldDeclsFromRaw(obj.Fields),
		})
	}
	return out, nil
}

// fpFieldDeclsFromRaw mirrors bootstrap.fieldDeclsFromRaw, including the
// child-side compound-parent synthesis used by source-backed app packs.
func fpFieldDeclsFromRaw(raw []fpFieldDeclRaw) []fieldprovenance.FieldDeclaration {
	out := make([]fieldprovenance.FieldDeclaration, 0, len(raw))
	indexByLowerName := map[string]int{}
	componentsByParent := map[string][]string{}
	parentNameByLower := map[string]string{}
	for _, r := range raw {
		name := r.apiName()
		if name == "" {
			continue
		}
		fd := fieldprovenance.FieldDeclaration{
			APIName:               name,
			IsCompound:            r.IsCompound,
			CompoundFieldName:     strings.TrimSpace(r.CompoundFieldName),
			CompoundComponentRole: strings.TrimSpace(r.CompoundComponentRole),
		}
		if len(r.ComposedFrom) > 0 {
			fd.ComposedFrom = append([]string(nil), r.ComposedFrom...)
		}
		indexByLowerName[canonicalFPFieldName(name)] = len(out)
		out = append(out, fd)

		parent := strings.TrimSpace(r.CompoundFieldName)
		parentKey := canonicalFPFieldName(parent)
		if parentKey == "" || parentKey == canonicalFPFieldName(name) {
			continue
		}
		if parentNameByLower[parentKey] == "" {
			parentNameByLower[parentKey] = parent
		}
		componentsByParent[parentKey] = append(componentsByParent[parentKey], name)
	}

	for parentKey, components := range componentsByParent {
		if len(components) == 0 {
			continue
		}
		if idx, ok := indexByLowerName[parentKey]; ok {
			out[idx].IsCompound = true
			if len(out[idx].ComposedFrom) == 0 {
				out[idx].ComposedFrom = append([]string(nil), components...)
			}
			continue
		}
		out = append(out, fieldprovenance.FieldDeclaration{
			APIName:      parentNameByLower[parentKey],
			IsCompound:   true,
			ComposedFrom: append([]string(nil), components...),
		})
	}
	return out
}

func canonicalFPFieldName(name string) string {
	return strings.ToLower(strings.TrimSpace(name))
}
