package bootstrap

import (
	"encoding/json"
	"os"
	"testing"

	"github.com/nexuscrm/backend/internal/controlplane/packspec"
	domainschema "github.com/nexuscrm/backend/internal/domain/schema"
	"github.com/nexuscrm/shared/pkg/constants"
)

type layoutContract struct {
	ObjectAPIName string   `json:"objectApiName"`
	CompactLayout []string `json:"compactLayout"`
	Sections      []struct {
		Fields []string `json:"fields"`
	} `json:"sections"`
}

func TestManagedDefinitionsCoverStandardUILayoutMetadata(t *testing.T) {
	defs := GetManagedTableDefinitions()
	defMap := make(map[string]bool, len(defs))
	for _, def := range defs {
		defMap[def.TableName] = true
	}
	// Also include standard objects from pack spec (no longer in GetManagedTableDefinitions)
	packSpecData, err := os.ReadFile("../controlplane/packspec/standard_objects_spec.json")
	if err != nil {
		t.Fatalf("failed to read standard_objects_spec.json: %v", err)
	}
	var packSpecs []struct {
		APIName string `json:"apiName"`
	}
	if err := json.Unmarshal(packSpecData, &packSpecs); err != nil {
		t.Fatalf("failed to parse standard_objects_spec.json: %v", err)
	}
	for _, spec := range packSpecs {
		defMap[spec.APIName] = true
	}

	layouts := loadStandardLayouts(t)
	for _, layout := range layouts {
		if !defMap[layout.ObjectAPIName] {
			t.Errorf("standard layout references missing managed object %q", layout.ObjectAPIName)
		}
	}

	listFieldsData, err := os.ReadFile("../controlplane/packs/standard_listfields.json")
	if err != nil {
		t.Fatalf("failed to read standard_listfields.json: %v", err)
	}
	var listFieldsByObject map[string][]string
	if err := json.Unmarshal(listFieldsData, &listFieldsByObject); err != nil {
		t.Fatalf("failed to parse standard_listfields.json: %v", err)
	}
	for objectAPIName := range listFieldsByObject {
		if !defMap[objectAPIName] {
			t.Errorf("standard list fields reference missing managed object %q", objectAPIName)
		}
	}
}

func TestStandardLayoutAndListFieldsMatchManagedSchema(t *testing.T) {
	defs := GetManagedTableDefinitions()
	fieldsByObject := make(map[string]map[string]bool, len(defs))
	for _, def := range defs {
		fieldSet := make(map[string]bool, len(def.Columns))
		for _, col := range def.Columns {
			fieldSet[col.Name] = true
		}
		fieldsByObject[def.TableName] = fieldSet
	}
	// Also include standard objects + fields from BOTH pack specs. Tenant
	// spec declares custom __c fields (and picklist deltas) that layouts and
	// list views may reference — fields declared there are seeded into
	// _System_Field by PlatformTenantPack alongside PlatformCorePack.
	loadSpec := func(path string) []struct {
		APIName string `json:"apiName"`
		Fields  []struct {
			APIName string `json:"apiName"`
		} `json:"fields"`
	} {
		raw, err := os.ReadFile(path)
		if err != nil {
			t.Fatalf("failed to read %s: %v", path, err)
		}
		var specs []struct {
			APIName string `json:"apiName"`
			Fields  []struct {
				APIName string `json:"apiName"`
			} `json:"fields"`
		}
		if err := json.Unmarshal(raw, &specs); err != nil {
			t.Fatalf("failed to parse %s: %v", path, err)
		}
		return specs
	}
	standardSpecs := loadSpec("../controlplane/packspec/standard_objects_spec.json")
	tenantSpecs := loadSpec("../controlplane/tenants/pingcap/tenant_objects_spec.json")

	// System fields that are auto-injected
	systemFields := []string{"Id", "OwnerId", "CreatedById", "LastModifiedById", "CreatedDate", "LastModifiedDate", "IsDeleted", "SystemModstamp"}
	for _, spec := range standardSpecs {
		fieldSet := make(map[string]bool)
		for _, sf := range systemFields {
			fieldSet[sf] = true
		}
		for _, f := range spec.Fields {
			fieldSet[f.APIName] = true
		}
		fieldsByObject[spec.APIName] = fieldSet
	}
	for _, spec := range tenantSpecs {
		fieldSet := fieldsByObject[spec.APIName]
		if fieldSet == nil {
			fieldSet = make(map[string]bool)
			for _, sf := range systemFields {
				fieldSet[sf] = true
			}
			fieldsByObject[spec.APIName] = fieldSet
		}
		for _, f := range spec.Fields {
			fieldSet[f.APIName] = true
		}
	}

	layouts := loadStandardLayouts(t)
	for _, layout := range layouts {
		fieldSet := fieldsByObject[layout.ObjectAPIName]
		for _, section := range layout.Sections {
			for _, field := range section.Fields {
				if !fieldSet[field] {
					t.Errorf("standard layout %q references missing field %s.%s", layout.ObjectAPIName, layout.ObjectAPIName, field)
				}
			}
		}
	}

	listFieldsData, err := os.ReadFile("../controlplane/packs/standard_listfields.json")
	if err != nil {
		t.Fatalf("failed to read standard_listfields.json: %v", err)
	}
	var listFieldsByObject map[string][]string
	if err := json.Unmarshal(listFieldsData, &listFieldsByObject); err != nil {
		t.Fatalf("failed to parse standard_listfields.json: %v", err)
	}
	for objectAPIName, fields := range listFieldsByObject {
		fieldSet := fieldsByObject[objectAPIName]
		for _, field := range fields {
			if fieldSet[field] || isAllowedCompoundNameAlias(objectAPIName, field) {
				continue
			}
			t.Errorf("standard list fields reference missing field %s.%s", objectAPIName, field)
		}
	}
}

func TestStandardOrderLayoutDoesNotExposeTenantArchivedFields(t *testing.T) {
	layouts := loadStandardLayouts(t)
	for _, layout := range layouts {
		if layout.ObjectAPIName != "Order" {
			continue
		}
		for _, field := range layout.CompactLayout {
			switch field {
			case "Archived_Order_Form__c", "StandardUploadUrl__c", "Archived__c":
				t.Fatalf("standard Order compact layout still exposes tenant-only field %q", field)
			}
		}
		for _, section := range layout.Sections {
			for _, field := range section.Fields {
				switch field {
				case "Archived_Order_Form__c", "StandardUploadUrl__c", "Archived__c":
					t.Fatalf("standard Order layout still exposes tenant-only field %q", field)
				}
			}
		}
		return
	}
	t.Fatal("standard Order layout not found")
}

// TestStandardObjectsInjectSystemFields moved to pack tests
// (standard objects now owned by PlatformCore pack)

func TestManagedDefinitionsKeepNotificationCreatedDateMetadata(t *testing.T) {
	defs := GetManagedTableDefinitions()
	for _, def := range defs {
		if def.TableName != "Notification" {
			continue
		}

		hasCreatedDate := false
		for _, col := range def.Columns {
			if col.Name == "CreatedDate" {
				hasCreatedDate = true
				break
			}
		}

		if !hasCreatedDate {
			t.Fatal("Notification is missing canonical CreatedDate metadata")
		}
		return
	}

	t.Fatal("Notification managed definition not found")
}

// TestStandardObjectsDeclareSharingModels moved to pack tests
// (standard objects now owned by PlatformCore pack)

func TestManagedRelationshipSemanticsDeclareSharingParents(t *testing.T) {
	t.Parallel()

	expectSharingParent := map[string]bool{
		"Contact|Account|AccountId":                true,
		"Contract|Account|AccountId":               true,
		"DashboardComponent|Dashboard|DashboardId": true,
		"Order|Account|AccountId":                  true,
		"Quote|Opportunity|OpportunityId":          true,
	}

	got := make(map[string]domainschema.ManagedRelationshipSemantics)
	// Pack spec relationships (Contact→Account, Order→Account, Quote→Opportunity)
	for _, def := range packspec.StandardRelationshipSemantics() {
		key := def.ChildObjectAPIName + "|" + def.ParentObjectAPIName + "|" + def.FieldAPIName
		got[key] = def
	}
	// Domain schema relationships (DashboardComponent→Dashboard — not a standard CRM object)
	for _, def := range domainschema.ManagedStandardRelationshipSemanticsList() {
		key := def.ChildObjectAPIName + "|" + def.ParentObjectAPIName + "|" + def.FieldAPIName
		if _, exists := got[key]; !exists {
			got[key] = def
		}
	}

	for key, want := range expectSharingParent {
		def, ok := got[key]
		if !ok {
			t.Fatalf("managed relationship semantics missing %s", key)
		}
		if def.IsSharingParent == nil || *def.IsSharingParent != want {
			t.Fatalf("%s IsSharingParent = %v, want %v", key, def.IsSharingParent, want)
		}
	}
}

func TestApprovalAutomationTablesIncludeIsDeleted(t *testing.T) {
	defs := GetManagedTableDefinitions()
	requiredTables := []string{constants.TableProcessNode, constants.TableProcessAction}

	for _, tableName := range requiredTables {
		var matched bool
		var isDeletedCount int
		for _, def := range defs {
			if def.TableName != tableName {
				continue
			}
			matched = true
			for _, col := range def.Columns {
				if col.Name == constants.FieldIsDeleted {
					isDeletedCount++
				}
			}
		}
		if !matched {
			t.Fatalf("managed schema missing %s definition", tableName)
		}
		if isDeletedCount != 1 {
			t.Errorf("%s should declare exactly one %s column, got %d", tableName, constants.FieldIsDeleted, isDeletedCount)
		}
	}
}

func TestApprovalWorkitemUsesHardDeleteMode(t *testing.T) {
	defs := GetManagedTableDefinitions()
	for _, def := range defs {
		if def.TableName != constants.TableProcessInstanceWorkitem {
			continue
		}
		if def.DeleteMode != "hard" {
			t.Fatalf("%s deleteMode = %q, want %q", constants.TableProcessInstanceWorkitem, def.DeleteMode, "hard")
		}
		return
	}

	t.Fatalf("managed schema missing %s definition", constants.TableProcessInstanceWorkitem)
}

func loadStandardLayouts(t *testing.T) []layoutContract {
	t.Helper()

	data, err := os.ReadFile("standard_layouts.json")
	if err != nil {
		t.Fatalf("failed to read standard_layouts.json: %v", err)
	}

	var layouts []layoutContract
	if err := json.Unmarshal(data, &layouts); err != nil {
		t.Fatalf("failed to parse standard_layouts.json: %v", err)
	}

	return layouts
}

func isAllowedCompoundNameAlias(objectAPIName string, field string) bool {
	if field != constants.FieldName {
		return false
	}

	switch objectAPIName {
	case "Contact", "Lead":
		return true
	default:
		return false
	}
}
