package dialectsfcrm

import (
	"encoding/json"

	"github.com/nexuscrm/dialect-sf-crm/wire"
	"github.com/nexuscrm/shared/pkg/models"
)

// RequiredRecordMutationPolicy is the CRM dialect's minimum compatible
// source-backed write-guard payload for one object. It is not generic L0 policy:
// it is an L1 distribution compatibility contract used by the host composition
// root to fail closed when an active CRM pack was not re-applied with the moved
// metadata-owned guards.
type RequiredRecordMutationPolicy struct {
	ObjectAPIName string
	Policy        models.RecordMutationAccessPolicy
}

// RequiredObjectLifecyclePolicy is the CRM dialect's minimum compatible
// source-backed lifecycle payload for one object. It is an L1 distribution
// contract: the host may verify that the active app-pack delivered it, but the
// generic platform must not author these CRM capability bindings itself.
type RequiredObjectLifecyclePolicy struct {
	ObjectAPIName string
	Policy        models.RecordLifecycleAccessPolicy
}

// connectorRemoteIdentityOwnerDeleteLifecycleCapability is owned by the L0
// automation lifecycle hook. These CRM object bindings keep the product shape
// in L1 while ensuring a deleted person cannot leave a finalized remote
// identity authoritative.
const connectorRemoteIdentityOwnerDeleteLifecycleCapability = "connector.remote-identity.owner-delete"

var requiredRecordMutationPolicies = []RequiredRecordMutationPolicy{
	{
		ObjectAPIName: "RecordType",
		Policy: models.RecordMutationAccessPolicy{Deny: []models.RecordMutationDenial{
			{
				Surfaces: []string{
					models.RecordMutationSurfaceSingleCreate,
					models.RecordMutationSurfaceSingleUpdate,
					models.RecordMutationSurfaceSingleDelete,
					models.RecordMutationSurfaceCollectionCreate,
					models.RecordMutationSurfaceCollectionUpdate,
					models.RecordMutationSurfaceCollectionDelete,
					models.RecordMutationSurfaceCollectionUpsert,
				},
				Message: "RecordType public standard REST mutation surface remains closed on this line",
			},
			{
				Surfaces:  []string{models.RecordMutationSurfaceExternalIDUpsert},
				Message:   "RecordType public standard REST mutation surface remains closed on this line",
				ErrorCode: wire.SFCodeInvalidFieldForInsertUpdate,
			},
			{
				Surfaces:  []string{models.RecordMutationSurfaceTreeCreate},
				Message:   "RecordType public mutations are not supported until the standard REST read/query contract is round-trippable",
				ErrorCode: wire.SFCodeInvalidRequest,
			},
		}},
	},
	{
		ObjectAPIName: "FlowDefinition",
		Policy: models.RecordMutationAccessPolicy{Deny: []models.RecordMutationDenial{{
			Surfaces: []string{
				models.RecordMutationSurfaceSingleCreate,
				models.RecordMutationSurfaceSingleUpdate,
				models.RecordMutationSurfaceExternalIDUpsert,
				models.RecordMutationSurfaceCollectionCreate,
				models.RecordMutationSurfaceCollectionUpdate,
				models.RecordMutationSurfaceCollectionUpsert,
				models.RecordMutationSurfaceTreeCreate,
			},
			Message: "FlowDefinition standard REST writes are closed after the Flow-native email notification cutover; use Flow Tooling, source-backed pack, or tenant overlay authoring so Flow validation can reject legacy emailAlert metadata and version activation bypasses",
		}}},
	},
	{
		ObjectAPIName: "Flow",
		Policy: models.RecordMutationAccessPolicy{Deny: []models.RecordMutationDenial{{
			Surfaces: []string{
				models.RecordMutationSurfaceSingleCreate,
				models.RecordMutationSurfaceSingleUpdate,
				models.RecordMutationSurfaceExternalIDUpsert,
				models.RecordMutationSurfaceCollectionCreate,
				models.RecordMutationSurfaceCollectionUpdate,
				models.RecordMutationSurfaceCollectionUpsert,
				models.RecordMutationSurfaceTreeCreate,
			},
			Message: "Flow standard REST writes are closed after the Flow-native email notification cutover; use Flow Tooling, source-backed pack, or tenant overlay authoring so Flow validation can reject legacy emailAlert metadata and version activation bypasses",
		}}},
	},
	{
		ObjectAPIName: "ProcessDefinition",
		Policy: models.RecordMutationAccessPolicy{Deny: []models.RecordMutationDenial{
			{
				Surfaces: []string{
					models.RecordMutationSurfaceSingleCreate,
					models.RecordMutationSurfaceExternalIDUpsert,
					models.RecordMutationSurfaceCollectionCreate,
					models.RecordMutationSurfaceCollectionUpdate,
					models.RecordMutationSurfaceCollectionUpsert,
					models.RecordMutationSurfaceTreeCreate,
				},
				Message: "ProcessDefinition public standard REST metadata writes are closed after the Flow-native email notification cutover; use ApprovalProcess metadata deploy or source-backed CRM pack authoring so approval child materialization remains pack-owned",
			},
			{
				Surfaces: []string{models.RecordMutationSurfaceSingleUpdate},
				Field:    "Metadata",
				Message:  "ProcessDefinition.Metadata public standard REST writes are closed; use ApprovalProcess metadata deploy or source-backed CRM pack authoring so approval child materialization remains pack-owned",
				When: &models.RecordMutationCondition{FieldPresent: &models.RecordMutationFieldPresentCondition{
					Field: "Metadata",
				}},
			},
			{
				Surfaces: []string{models.RecordMutationSurfaceSingleUpdate},
				Field:    "State",
				Message:  "approval process activation cannot enable retired ProcessAction.Alert actions; migrate approval notifications to Flow sendEmail and rerun migrate-workflow-alerts-to-flows verify-zero",
				When: &models.RecordMutationCondition{AllOf: []models.RecordMutationCondition{
					{FieldPresentEquals: &models.RecordMutationFieldEqualsCondition{
						Field:           "State",
						Value:           "Active",
						CaseInsensitive: true,
					}},
					{RelatedExists: &models.RecordMutationRelatedExistsCondition{
						ObjectAPIName: "ProcessAction",
						ParentField:   "ProcessDefinitionId",
						Match: []models.RecordMutationFieldEqualsCondition{{
							Field:           "ActionType",
							Value:           "Alert",
							CaseInsensitive: true,
						}},
					}},
				}},
			},
			{
				Surfaces: []string{models.RecordMutationSurfaceSingleUpdate},
				Field:    "IsActive",
				Message:  "approval process activation cannot enable retired ProcessAction.Alert actions; migrate approval notifications to Flow sendEmail and rerun migrate-workflow-alerts-to-flows verify-zero",
				When: &models.RecordMutationCondition{AllOf: []models.RecordMutationCondition{
					{FieldPresentEquals: &models.RecordMutationFieldEqualsCondition{
						Field:           "IsActive",
						Value:           "true",
						CaseInsensitive: true,
					}},
					{RelatedExists: &models.RecordMutationRelatedExistsCondition{
						ObjectAPIName: "ProcessAction",
						ParentField:   "ProcessDefinitionId",
						Match: []models.RecordMutationFieldEqualsCondition{{
							Field:           "ActionType",
							Value:           "Alert",
							CaseInsensitive: true,
						}},
					}},
				}},
			},
		}},
	},
	{
		ObjectAPIName: "ProcessNode",
		Policy: models.RecordMutationAccessPolicy{Deny: []models.RecordMutationDenial{{
			Surfaces: []string{
				models.RecordMutationSurfaceSingleCreate,
				models.RecordMutationSurfaceSingleUpdate,
				models.RecordMutationSurfaceSingleDelete,
				models.RecordMutationSurfaceExternalIDUpsert,
				models.RecordMutationSurfaceCollectionCreate,
				models.RecordMutationSurfaceCollectionUpdate,
				models.RecordMutationSurfaceCollectionDelete,
				models.RecordMutationSurfaceCollectionUpsert,
				models.RecordMutationSurfaceTreeCreate,
			},
			Message: "ProcessNode public standard REST writes are closed; use ApprovalProcess metadata deploy or source-backed CRM pack authoring so approval child materialization remains metadata-owned",
		}}},
	},
	{
		ObjectAPIName: "ProcessAction",
		Policy: models.RecordMutationAccessPolicy{Deny: []models.RecordMutationDenial{{
			Surfaces: []string{
				models.RecordMutationSurfaceSingleCreate,
				models.RecordMutationSurfaceSingleUpdate,
				models.RecordMutationSurfaceSingleDelete,
				models.RecordMutationSurfaceExternalIDUpsert,
				models.RecordMutationSurfaceCollectionCreate,
				models.RecordMutationSurfaceCollectionUpdate,
				models.RecordMutationSurfaceCollectionDelete,
				models.RecordMutationSurfaceCollectionUpsert,
				models.RecordMutationSurfaceTreeCreate,
			},
			Message: "ProcessAction public standard REST writes are closed; use ApprovalProcess metadata deploy or source-backed CRM pack authoring so approval child materialization remains metadata-owned",
		}}},
	},
	{
		// IdentitySyncSetting is a singleton L2 deployment-policy record. The
		// normal package apply path writes it under system context; public CRUD
		// must not become a second, administrator-editable authoring surface.
		ObjectAPIName: "IdentitySyncSetting",
		Policy: models.RecordMutationAccessPolicy{Deny: []models.RecordMutationDenial{{
			Surfaces: []string{
				models.RecordMutationSurfaceSingleCreate,
				models.RecordMutationSurfaceSingleUpdate,
				models.RecordMutationSurfaceSingleDelete,
				models.RecordMutationSurfaceExternalIDUpsert,
				models.RecordMutationSurfaceCollectionCreate,
				models.RecordMutationSurfaceCollectionUpdate,
				models.RecordMutationSurfaceCollectionDelete,
				models.RecordMutationSurfaceCollectionUpsert,
				models.RecordMutationSurfaceTreeCreate,
			},
			Message: "IdentitySyncSetting is L2 deployment policy; use the installed L2 pack seed rather than public REST CRUD",
		}}},
	},
	{
		// The native SCIM AppService reserves this internal object directly through
		// its neutral Runtime mutation port. Public Salesforce-compatible CRUD must
		// not offer a second path that can forge or remove fleet-wide safety slots.
		ObjectAPIName: "IdentitySyncDeactivationTicket",
		Policy: models.RecordMutationAccessPolicy{Deny: []models.RecordMutationDenial{{
			Surfaces: []string{
				models.RecordMutationSurfaceSingleCreate,
				models.RecordMutationSurfaceSingleUpdate,
				models.RecordMutationSurfaceSingleDelete,
				models.RecordMutationSurfaceExternalIDUpsert,
				models.RecordMutationSurfaceCollectionCreate,
				models.RecordMutationSurfaceCollectionUpdate,
				models.RecordMutationSurfaceCollectionDelete,
				models.RecordMutationSurfaceCollectionUpsert,
				models.RecordMutationSurfaceTreeCreate,
			},
			Message: "IdentitySyncDeactivationTicket is internal SCIM safety state; public REST CRUD is not permitted",
		}}},
	},
}

var requiredObjectLifecyclePolicies = []RequiredObjectLifecyclePolicy{
	{
		ObjectAPIName: "Lead",
		Policy: models.RecordLifecycleAccessPolicy{Hooks: []models.RecordLifecycleHookPolicy{
			{
				Phase:      "after_delete_in_tx",
				Capability: connectorRemoteIdentityOwnerDeleteLifecycleCapability,
			},
			{
				Phase:      "after_update_in_tx",
				Capability: connectorRemoteIdentityOwnerDeleteLifecycleCapability,
			},
		}},
	},
	{
		ObjectAPIName: "Contact",
		Policy: models.RecordLifecycleAccessPolicy{Hooks: []models.RecordLifecycleHookPolicy{
			{
				Phase:      "after_delete_in_tx",
				Capability: connectorRemoteIdentityOwnerDeleteLifecycleCapability,
			},
			{
				Phase:      "after_update_in_tx",
				Capability: connectorRemoteIdentityOwnerDeleteLifecycleCapability,
			},
		}},
	},
	{
		ObjectAPIName: "ProcessDefinition",
		Policy: models.RecordLifecycleAccessPolicy{Hooks: []models.RecordLifecycleHookPolicy{
			{
				Phase:      "after_create_in_tx",
				Capability: ApprovalProcessChildSyncCapability,
			},
			{
				Phase:      "after_update_in_tx",
				Capability: ApprovalProcessChildSyncCapability,
			},
		}},
	},
}

// rolloutCompatibleRecordMutationPolicies is a temporary binary-first rollout
// bridge for active CRM installs that have not yet re-applied the paired app-pack
// payload. The host still verifies ownership and live==sidecar; this list only
// keeps the previous CRM-owned ProcessDefinition contract accepted long enough
// for the app-pack revision carrying the stricter field/related-exists rules to
// be applied after the binary upgrade. Do not add generic host policy here: CRM
// policy compatibility remains an L1 distribution fact.
var rolloutCompatibleRecordMutationPolicies = []RequiredRecordMutationPolicy{
	{
		ObjectAPIName: "ProcessDefinition",
		Policy: models.RecordMutationAccessPolicy{Deny: []models.RecordMutationDenial{{
			Surfaces: []string{
				models.RecordMutationSurfaceExternalIDUpsert,
				models.RecordMutationSurfaceCollectionCreate,
				models.RecordMutationSurfaceCollectionUpdate,
				models.RecordMutationSurfaceCollectionUpsert,
				models.RecordMutationSurfaceTreeCreate,
			},
			Field:   "ProcessDefinition",
			Message: "ProcessDefinition generic standard REST writes are closed after the Flow-native email notification cutover; use ApprovalProcess metadata or direct ProcessDefinition create/update so approval child sync and retired ProcessAction.Alert activation validation run before enabling the process",
		}}},
	},
	{
		ObjectAPIName: "ProcessAction",
		Policy: models.RecordMutationAccessPolicy{Deny: []models.RecordMutationDenial{{
			Surfaces: []string{
				models.RecordMutationSurfaceSingleCreate,
				models.RecordMutationSurfaceSingleUpdate,
				models.RecordMutationSurfaceExternalIDUpsert,
				models.RecordMutationSurfaceCollectionCreate,
				models.RecordMutationSurfaceCollectionUpdate,
				models.RecordMutationSurfaceCollectionUpsert,
				models.RecordMutationSurfaceTreeCreate,
			},
			Field:   "ActionType",
			Message: "ProcessAction standard REST writes are closed for retired ProcessAction.Alert after the Flow-native email notification cutover; use approval Flow actions for notifications while FieldUpdate and Flow ProcessAction writes remain supported, then rerun migrate-workflow-alerts-to-flows verify-zero",
			When: &models.RecordMutationCondition{
				FieldEquals: &models.RecordMutationFieldEqualsCondition{
					Field:           "ActionType",
					Value:           "Alert",
					CaseInsensitive: true,
				},
			},
		}}},
	},
}

// RequiredRecordMutationPolicies returns the CRM-owned write-guard policy
// payload that must be present in live metadata before a CRM distribution serves
// active CRM routes. The list lives with the CRM dialect/module rather than the
// generic host so the composition root can fail closed without owning CRM
// vocabulary itself.
func RequiredRecordMutationPolicies() []RequiredRecordMutationPolicy {
	out := make([]RequiredRecordMutationPolicy, 0, len(requiredRecordMutationPolicies))
	for _, policy := range requiredRecordMutationPolicies {
		out = append(out, RequiredRecordMutationPolicy{
			ObjectAPIName: policy.ObjectAPIName,
			Policy:        cloneRecordMutationPolicy(policy.Policy),
		})
	}
	return out
}

// RolloutCompatibleRecordMutationPolicies returns CRM-owned policy contracts
// accepted during a binary-first warm-upgrade window. These are not replacement
// SSoTs: the latest required policy above remains the target, and the host still
// requires the live metadata to match the pack sidecar exactly.
func RolloutCompatibleRecordMutationPolicies() []RequiredRecordMutationPolicy {
	out := make([]RequiredRecordMutationPolicy, 0, len(rolloutCompatibleRecordMutationPolicies))
	for _, policy := range rolloutCompatibleRecordMutationPolicies {
		out = append(out, RequiredRecordMutationPolicy{
			ObjectAPIName: policy.ObjectAPIName,
			Policy:        cloneRecordMutationPolicy(policy.Policy),
		})
	}
	return out
}

func RequiredRecordMutationPolicyObjects() []string {
	policies := RequiredRecordMutationPolicies()
	out := make([]string, 0, len(policies))
	for _, policy := range policies {
		out = append(out, policy.ObjectAPIName)
	}
	return out
}

// RequiredObjectLifecyclePolicies returns the CRM-owned lifecycle payload that
// must be present in live metadata before a CRM distribution relies on
// lifecycle-owned child materialization.
func RequiredObjectLifecyclePolicies() []RequiredObjectLifecyclePolicy {
	out := make([]RequiredObjectLifecyclePolicy, 0, len(requiredObjectLifecyclePolicies))
	for _, policy := range requiredObjectLifecyclePolicies {
		out = append(out, RequiredObjectLifecyclePolicy{
			ObjectAPIName: policy.ObjectAPIName,
			Policy:        cloneLifecyclePolicy(policy.Policy),
		})
	}
	return out
}

func cloneRecordMutationPolicy(policy models.RecordMutationAccessPolicy) models.RecordMutationAccessPolicy {
	raw, err := json.Marshal(policy)
	if err != nil {
		panic(err)
	}
	var cloned models.RecordMutationAccessPolicy
	if err := json.Unmarshal(raw, &cloned); err != nil {
		panic(err)
	}
	return cloned
}

func cloneLifecyclePolicy(policy models.RecordLifecycleAccessPolicy) models.RecordLifecycleAccessPolicy {
	raw, err := json.Marshal(policy)
	if err != nil {
		panic(err)
	}
	var cloned models.RecordLifecycleAccessPolicy
	if err := json.Unmarshal(raw, &cloned); err != nil {
		panic(err)
	}
	return cloned
}
