/**
 * Test: Registry Validation Utilities
 * Tests for ExecutionInterface-to-inputSchema validation
 */

import { afterEach, describe, it, expect, vi } from 'vitest'
import { z } from 'zod'
import {
  validateExecutionInterface,
  validateDeclaredSystemInterfaceReadiness,
  validateResourceGovernance,
  detectMissingApiInterfaceDeclarations,
  RegistryValidationError
} from '../validation'
import { ResourceRegistry } from '../resource-registry'
import type { WorkflowDefinition } from '../../../execution/engine/workflow/types'
import type { AgentDefinition } from '../../../execution/engine/agent/core/types'
import type { OrganizationRegistry } from '../resource-registry'
import type { ResourceEntry } from '../../../organization-model/domains/resources'

describe('validateExecutionInterface', () => {
  describe('required field validation', () => {
    it('should pass when all required schema fields have form fields', () => {
      const inputSchema = z.object({
        name: z.string(),
        email: z.string()
      })

      const executionInterface = {
        form: {
          fields: [
            { name: 'name', required: true },
            { name: 'email', required: true }
          ]
        }
      }

      expect(() => {
        validateExecutionInterface('test-org', 'test-resource', executionInterface, inputSchema)
      }).not.toThrow()
    })

    it('should throw when required schema field is missing from form', () => {
      const inputSchema = z.object({
        name: z.string(),
        email: z.string()
      })

      const executionInterface = {
        form: {
          fields: [
            { name: 'name', required: true }
            // Missing 'email' field
          ]
        }
      }

      expect(() => {
        validateExecutionInterface('test-org', 'test-resource', executionInterface, inputSchema)
      }).toThrow(RegistryValidationError)
      expect(() => {
        validateExecutionInterface('test-org', 'test-resource', executionInterface, inputSchema)
      }).toThrow('missing required field "email"')
    })

    it('should throw when form field is not marked required but schema field is required', () => {
      const inputSchema = z.object({
        name: z.string() // Required
      })

      const executionInterface = {
        form: {
          fields: [
            { name: 'name', required: false } // Not marked required
          ]
        }
      }

      expect(() => {
        validateExecutionInterface('test-org', 'test-resource', executionInterface, inputSchema)
      }).toThrow(RegistryValidationError)
      expect(() => {
        validateExecutionInterface('test-org', 'test-resource', executionInterface, inputSchema)
      }).toThrow('should be required')
    })

    it('should pass when optional schema field is missing from form', () => {
      const inputSchema = z.object({
        name: z.string(),
        description: z.string().optional()
      })

      const executionInterface = {
        form: {
          fields: [
            { name: 'name', required: true }
            // 'description' is optional, so OK to omit
          ]
        }
      }

      expect(() => {
        validateExecutionInterface('test-org', 'test-resource', executionInterface, inputSchema)
      }).not.toThrow()
    })

    it('should pass when optional schema field has non-required form field', () => {
      const inputSchema = z.object({
        name: z.string(),
        description: z.string().optional()
      })

      const executionInterface = {
        form: {
          fields: [
            { name: 'name', required: true },
            { name: 'description', required: false }
          ]
        }
      }

      expect(() => {
        validateExecutionInterface('test-org', 'test-resource', executionInterface, inputSchema)
      }).not.toThrow()
    })
  })

  describe('field mapping validation', () => {
    it('should pass with valid field mappings', () => {
      const inputSchema = z.object({
        taskName: z.string(),
        priority: z.string()
      })

      const executionInterface = {
        form: {
          fields: [
            { name: 'task_name', required: true },
            { name: 'prio', required: true }
          ],
          fieldMappings: {
            task_name: 'taskName',
            prio: 'priority'
          }
        }
      }

      expect(() => {
        validateExecutionInterface('test-org', 'test-resource', executionInterface, inputSchema)
      }).not.toThrow()
    })

    it('should throw when field mapping points to non-existent schema field', () => {
      const inputSchema = z.object({
        name: z.string()
      })

      // Include a valid mapping for 'name' so we get past the required field check,
      // then add an extra field that maps to a non-existent schema field
      const executionInterface = {
        form: {
          fields: [
            { name: 'userName', required: true },
            { name: 'extraField', required: false }
          ],
          fieldMappings: {
            userName: 'name', // Valid mapping
            extraField: 'nonExistentField' // Invalid mapping
          }
        }
      }

      expect(() => {
        validateExecutionInterface('test-org', 'test-resource', executionInterface, inputSchema)
      }).toThrow(RegistryValidationError)
      expect(() => {
        validateExecutionInterface('test-org', 'test-resource', executionInterface, inputSchema)
      }).toThrow('maps to non-existent schema field "nonExistentField"')
    })

    it('should throw when form field without mapping has no matching schema field', () => {
      const inputSchema = z.object({
        name: z.string()
      })

      const executionInterface = {
        form: {
          fields: [
            { name: 'name', required: true },
            { name: 'unknownField', required: false }
          ]
        }
      }

      expect(() => {
        validateExecutionInterface('test-org', 'test-resource', executionInterface, inputSchema)
      }).toThrow(RegistryValidationError)
      expect(() => {
        validateExecutionInterface('test-org', 'test-resource', executionInterface, inputSchema)
      }).toThrow('maps to non-existent schema field "unknownField"')
    })

    it('should throw with flatten suggestion when form field uses nested notation', () => {
      const inputSchema = z.object({
        name: z.string()
      })

      const executionInterface = {
        form: {
          fields: [
            { name: 'name', required: true },
            { name: 'criteria.targetTitles', required: true }
          ]
        }
      }

      expect(() => {
        validateExecutionInterface('test-org', 'test-resource', executionInterface, inputSchema)
      }).toThrow(RegistryValidationError)
      expect(() => {
        validateExecutionInterface('test-org', 'test-resource', executionInterface, inputSchema)
      }).toThrow('uses nested notation')
      expect(() => {
        validateExecutionInterface('test-org', 'test-resource', executionInterface, inputSchema)
      }).toThrow('Flatten the inputSchema')
    })
  })

  describe('default values (effectively optional)', () => {
    it('should treat fields with defaults as optional', () => {
      const inputSchema = z.object({
        name: z.string(),
        priority: z.string().default('medium')
      })

      const executionInterface = {
        form: {
          fields: [
            { name: 'name', required: true }
            // 'priority' has default, so OK to omit
          ]
        }
      }

      expect(() => {
        validateExecutionInterface('test-org', 'test-resource', executionInterface, inputSchema)
      }).not.toThrow()
    })
  })

  describe('enum fields', () => {
    it('should validate enum fields correctly', () => {
      const inputSchema = z.object({
        priority: z.enum(['low', 'medium', 'high'])
      })

      const executionInterface = {
        form: {
          fields: [{ name: 'priority', required: true }]
        }
      }

      expect(() => {
        validateExecutionInterface('test-org', 'test-resource', executionInterface, inputSchema)
      }).not.toThrow()
    })
  })

  describe('empty schema', () => {
    it('should pass with empty schema and no form fields', () => {
      const inputSchema = z.object({})

      const executionInterface = {
        form: {
          fields: []
        }
      }

      expect(() => {
        validateExecutionInterface('test-org', 'test-resource', executionInterface, inputSchema)
      }).not.toThrow()
    })
  })
})

describe('validateResourceGovernance', () => {
  afterEach(() => {
    vi.unstubAllEnvs()
  })

  const systemA = {
    id: 'sys.lead-gen',
    title: 'Lead Gen',
    description: 'Lead generation system.',
    kind: 'operational' as const,
    governedByKnowledge: [],
    drivesGoals: [],
    status: 'active' as const,
    order: 10
  }

  const systemB = {
    id: 'sys.crm',
    title: 'CRM',
    description: 'CRM system.',
    kind: 'operational' as const,
    governedByKnowledge: [],
    drivesGoals: [],
    status: 'active' as const,
    order: 20
  }

  const workflowResource: ResourceEntry = {
    id: 'lead-import',
    kind: 'workflow',
    systemPath: 'sys.lead-gen',
    status: 'active',
    order: 10
  }

  const actionBackedWorkflowResource: ResourceEntry = {
    ...workflowResource,
    ontology: {
      actions: ['sys.lead-gen:action/import-leads'],
      primaryAction: 'sys.lead-gen:action/import-leads',
      reads: ['sys.lead-gen:object/company'],
      usesCatalogs: ['sys.lead-gen:catalog/source'],
      emits: ['sys.lead-gen:event/imported']
    }
  }

  const agentResource: ResourceEntry = {
    id: 'lead-import',
    kind: 'agent',
    systemPath: 'sys.lead-gen',
    status: 'active',
    agentKind: 'specialist',
    sessionCapable: false,
    order: 10
  }

  const createGovernedWorkflow = (
    resource = workflowResource,
    overrides: Partial<WorkflowDefinition['config']> = {}
  ): WorkflowDefinition => ({
    config: {
      resourceId: resource.id,
      name: `Workflow ${resource.id}`,
      description: `Test workflow ${resource.id}`,
      version: '1.0.0',
      type: 'workflow',
      status: 'dev',
      resource: resource as Extract<ResourceEntry, { kind: 'workflow' }>,
      ...overrides
    },
    contract: {
      inputSchema: z.object({ data: z.string() }),
      outputSchema: z.object({ result: z.boolean() })
    },
    steps: {},
    entryPoint: 'start'
  })

  const createRawWorkflow = (resourceId = workflowResource.id): WorkflowDefinition => ({
    config: {
      resourceId,
      name: `Workflow ${resourceId}`,
      description: `Test workflow ${resourceId}`,
      version: '1.0.0',
      type: 'workflow',
      status: 'dev'
    },
    contract: {
      inputSchema: z.object({ data: z.string() }),
      outputSchema: z.object({ result: z.boolean() })
    },
    steps: {},
    entryPoint: 'start'
  })

  const validOntology = {
    objectTypes: {
      'sys.lead-gen:object/company': {
        id: 'sys.lead-gen:object/company',
        label: 'Company'
      }
    },
    actionTypes: {
      'sys.lead-gen:action/import-leads': {
        id: 'sys.lead-gen:action/import-leads',
        label: 'Import leads'
      }
    },
    catalogTypes: {
      'sys.lead-gen:catalog/source': {
        id: 'sys.lead-gen:catalog/source',
        label: 'Source'
      }
    },
    eventTypes: {
      'sys.lead-gen:event/imported': {
        id: 'sys.lead-gen:event/imported',
        label: 'Imported'
      }
    }
  }

  const createModel = (
    resources: ResourceEntry[] = [workflowResource],
    systems = [systemA],
    extras: Record<string, unknown> = {}
  ) => ({
    systems: Object.fromEntries(systems.map((s) => [s.id, s])),
    resources: Object.fromEntries(resources.map((r) => [r.id, r])),
    ...extras
  })

  it('passes when code resources are descriptor-backed and match active OM Resources and Systems', () => {
    const result = validateResourceGovernance(
      'test-org',
      {
        version: '1.0.0',
        workflows: [createGovernedWorkflow()]
      },
      createModel(),
      { mode: 'strict' }
    )

    expect(result.valid).toBe(true)
    expect(result.issues).toEqual([])
  })

  it('allows active platform agents to be registered outside the SDK deployment bundle', () => {
    const platformAgentResource: ResourceEntry = {
      id: 'command-center-assistant',
      kind: 'agent',
      systemPath: 'sys.lead-gen',
      status: 'active',
      agentKind: 'platform',
      sessionCapable: true,
      order: 20
    }

    const result = validateResourceGovernance(
      'test-org',
      {
        version: '1.0.0',
        workflows: [createGovernedWorkflow()]
      },
      createModel([workflowResource, platformAgentResource]),
      { mode: 'strict' }
    )

    expect(result.valid).toBe(true)
    expect(result.issues).toEqual([])
  })

  it('throws in strict mode when an active OM resource has no code-side resource', () => {
    expect(() =>
      validateResourceGovernance(
        'test-org',
        {
          version: '1.0.0',
          workflows: []
        },
        createModel(),
        { mode: 'strict' }
      )
    ).toThrow("OM resource 'lead-import' has no matching code-side resource")
  })

  it('defaults to strict mode after cutover', () => {
    expect(() =>
      validateResourceGovernance(
        'test-org',
        {
          version: '1.0.0',
          workflows: []
        },
        createModel()
      )
    ).toThrow("OM resource 'lead-import' has no matching code-side resource")
  })

  it('downgrades strict failures to warnings in warn-only mode', () => {
    const warnings: string[] = []

    const result = validateResourceGovernance(
      'test-org',
      {
        version: '1.0.0',
        workflows: []
      },
      createModel(),
      {
        mode: 'warn-only',
        onWarning: (issue) => warnings.push(issue.message)
      }
    )

    expect(result.valid).toBe(false)
    expect(result.issues.map((issue) => issue.type)).toContain('missing-code-resource')
    expect(warnings).toEqual(["[test-org] OM resource 'lead-import' has no matching code-side resource."])
  })

  it('honors ELEVASIS_RESOURCE_VALIDATOR=warn-only as the permanent escape hatch', () => {
    vi.stubEnv('ELEVASIS_RESOURCE_VALIDATOR', 'warn-only')
    const warnings: string[] = []

    const result = validateResourceGovernance(
      'test-org',
      {
        version: '1.0.0',
        workflows: []
      },
      createModel(),
      {
        onWarning: (issue) => warnings.push(issue.message)
      }
    )

    expect(result.valid).toBe(false)
    expect(result.mode).toBe('warn-only')
    expect(result.issues.map((issue) => issue.type)).toContain('missing-code-resource')
    expect(warnings).toEqual(["[test-org] OM resource 'lead-import' has no matching code-side resource."])
  })

  it('reports a code-side resource with no active OM resource', () => {
    const extraResource: ResourceEntry = {
      ...workflowResource,
      id: 'extra-workflow'
    }

    expect(() =>
      validateResourceGovernance(
        'test-org',
        {
          version: '1.0.0',
          workflows: [createGovernedWorkflow(extraResource)]
        },
        createModel([], [systemA]),
        { mode: 'strict' }
      )
    ).toThrow("Code-side resource 'extra-workflow' has no active OM Resource descriptor")
  })

  it('reports runtime/OM type mismatches', () => {
    expect(() =>
      validateResourceGovernance(
        'test-org',
        {
          version: '1.0.0',
          workflows: [createRawWorkflow('lead-import')]
        },
        createModel([agentResource]),
        { mode: 'strict' }
      )
    ).toThrow("Resource 'lead-import' type mismatch: code has 'workflow', OM has 'agent'")
  })

  it('reports descriptor/OM system mismatches', () => {
    const codeDescriptor: ResourceEntry = {
      ...workflowResource,
      systemPath: 'sys.crm'
    }

    expect(() =>
      validateResourceGovernance(
        'test-org',
        {
          version: '1.0.0',
          workflows: [createGovernedWorkflow(codeDescriptor)]
        },
        createModel([workflowResource], [systemA, systemB]),
        { mode: 'strict' }
      )
    ).toThrow("Resource 'lead-import' system mismatch: code descriptor has 'sys.crm', OM has 'sys.lead-gen'")
  })

  it('reports descriptor/OM ontology mismatches', () => {
    const codeDescriptor: ResourceEntry = {
      ...workflowResource,
      ontology: undefined
    }

    expect(() =>
      validateResourceGovernance(
        'test-org',
        {
          version: '1.0.0',
          workflows: [createGovernedWorkflow(codeDescriptor)]
        },
        createModel([actionBackedWorkflowResource], [systemA], { ontology: validOntology }),
        { mode: 'strict' }
      )
    ).toThrow("Resource 'lead-import' ontology descriptor mismatch between code and OM")
  })

  it('reports missing ontology action refs against compiled OM records', () => {
    const resourceWithMissingAction: ResourceEntry = {
      ...workflowResource,
      ontology: {
        actions: ['sys.lead-gen:action/missing'],
        primaryAction: 'sys.lead-gen:action/missing'
      }
    }

    expect(() =>
      validateResourceGovernance(
        'test-org',
        {
          version: '1.0.0',
          workflows: [createGovernedWorkflow(resourceWithMissingAction)]
        },
        createModel([resourceWithMissingAction], [systemA], { ontology: validOntology }),
        { mode: 'strict' }
      )
    ).toThrow(
      "Resource 'lead-import' ontology.actions references missing action ontology record 'sys.lead-gen:action/missing'"
    )
  })

  it('reports new ontology diagnostics without throwing in warn-only mode', () => {
    const warnings: string[] = []
    const resourceWithMissingAction: ResourceEntry = {
      ...workflowResource,
      ontology: {
        actions: ['sys.lead-gen:action/missing'],
        primaryAction: 'sys.lead-gen:action/missing'
      }
    }

    const result = validateResourceGovernance(
      'test-org',
      {
        version: '1.0.0',
        workflows: [createGovernedWorkflow(resourceWithMissingAction)]
      },
      createModel([resourceWithMissingAction], [systemA], { ontology: validOntology }),
      {
        mode: 'warn-only',
        onWarning: (issue) => warnings.push(issue.message)
      }
    )

    expect(result.valid).toBe(false)
    expect(result.issues.map((issue) => issue.type)).toContain('ontology-reference-missing')
    expect(warnings).toContain(
      "[test-org] Resource 'lead-import' ontology.actions references missing action ontology record 'sys.lead-gen:action/missing'."
    )
  })

  it('reports primaryAction values outside ontology.actions', () => {
    const resourceWithMismatchedPrimaryAction: ResourceEntry = {
      ...workflowResource,
      ontology: {
        actions: ['sys.lead-gen:action/import-leads'],
        primaryAction: 'sys.lead-gen:action/other'
      }
    }

    expect(() =>
      validateResourceGovernance(
        'test-org',
        {
          version: '1.0.0',
          workflows: [createGovernedWorkflow(resourceWithMismatchedPrimaryAction)]
        },
        createModel([resourceWithMismatchedPrimaryAction], [systemA], { ontology: validOntology }),
        { mode: 'strict' }
      )
    ).toThrow("Resource 'lead-import' primaryAction 'sys.lead-gen:action/other' must be included in ontology.actions")
  })

  it('reports action-backed workflow descriptors that omit ontology.actions', () => {
    const resourceWithoutActions: ResourceEntry = {
      ...workflowResource,
      ontology: {
        reads: ['sys.lead-gen:object/company']
      }
    }

    expect(() =>
      validateResourceGovernance(
        'test-org',
        {
          version: '1.0.0',
          workflows: [createGovernedWorkflow(resourceWithoutActions)]
        },
        createModel([resourceWithoutActions], [systemA], { ontology: validOntology }),
        { mode: 'strict' }
      )
    ).toThrow("Resource 'lead-import' declares ontology bindings but no ontology actions")
  })

  it('accepts contract-only workflow ontology bindings without ontology.actions', () => {
    const contractOnlyResource: ResourceEntry = {
      ...workflowResource,
      ontology: {
        contract: {
          input: '@repo/elevasis-core/contracts/full-diagnostic#inputSchema'
        }
      }
    }

    const result = validateResourceGovernance(
      'test-org',
      {
        version: '1.0.0',
        workflows: [createGovernedWorkflow(contractOnlyResource)]
      },
      createModel([contractOnlyResource], [systemA], { ontology: validOntology }),
      { mode: 'strict' }
    )

    expect(result.valid).toBe(true)
  })

  it('rejects sibling-System ontology refs without an exact scoped topology grant', () => {
    const crmDealOntologyId = 'sys.crm:object/deal'
    const resourceWithForeignRead: ResourceEntry = {
      ...workflowResource,
      ontology: {
        actions: ['sys.lead-gen:action/import-leads'],
        primaryAction: 'sys.lead-gen:action/import-leads',
        reads: [crmDealOntologyId]
      }
    }

    expect(() =>
      validateResourceGovernance(
        'test-org',
        {
          version: '1.0.0',
          workflows: [createGovernedWorkflow(resourceWithForeignRead)]
        },
        createModel([resourceWithForeignRead], [systemA, systemB], {
          ontology: {
            ...validOntology,
            objectTypes: {
              ...validOntology.objectTypes,
              [crmDealOntologyId]: {
                id: crmDealOntologyId,
                label: 'Deal'
              }
            }
          },
          topology: {
            version: 1,
            relationships: {
              'lead-gen-uses-crm': {
                from: { kind: 'system', id: 'sys.lead-gen' },
                kind: 'uses',
                to: { kind: 'system', id: 'sys.crm' }
              }
            }
          }
        }),
        { mode: 'strict' }
      )
    ).toThrow(
      "Resource 'lead-import' in system 'sys.lead-gen' references ontology 'sys.crm:object/deal' from system 'sys.crm' without scoped topology grant"
    )
  })

  it('reports scoped topology grant diagnostics in warn-only mode', () => {
    const warnings: string[] = []
    const crmDealOntologyId = 'sys.crm:object/deal'
    const resourceWithForeignRead: ResourceEntry = {
      ...workflowResource,
      ontology: {
        actions: ['sys.lead-gen:action/import-leads'],
        primaryAction: 'sys.lead-gen:action/import-leads',
        reads: [crmDealOntologyId]
      }
    }

    const result = validateResourceGovernance(
      'test-org',
      {
        version: '1.0.0',
        workflows: [createGovernedWorkflow(resourceWithForeignRead)]
      },
      createModel([resourceWithForeignRead], [systemA, systemB], {
        ontology: {
          ...validOntology,
          objectTypes: {
            ...validOntology.objectTypes,
            [crmDealOntologyId]: {
              id: crmDealOntologyId,
              label: 'Deal'
            }
          }
        }
      }),
      {
        mode: 'warn-only',
        onWarning: (issue) => warnings.push(issue.message)
      }
    )

    expect(result.valid).toBe(false)
    expect(result.issues).toEqual([
      expect.objectContaining({
        type: 'ontology-topology-grant-missing',
        resourceId: 'lead-import',
        message: expect.stringContaining("system 'sys.lead-gen'")
      })
    ])
    expect(warnings[0]).toContain("ontology 'sys.crm:object/deal'")
    expect(warnings[0]).toContain("from system 'sys.crm'")
    expect(warnings[0]).toContain('systemInterfaceGrant(sys.lead-gen -> sys.crm')
  })

  it('accepts sibling-System ontology refs with an exact scoped topology grant', () => {
    const crmDealOntologyId = 'sys.crm:object/deal'
    const resourceWithForeignRead: ResourceEntry = {
      ...workflowResource,
      ontology: {
        actions: ['sys.lead-gen:action/import-leads'],
        primaryAction: 'sys.lead-gen:action/import-leads',
        reads: [crmDealOntologyId]
      }
    }

    const result = validateResourceGovernance(
      'test-org',
      {
        version: '1.0.0',
        workflows: [createGovernedWorkflow(resourceWithForeignRead)]
      },
      createModel([resourceWithForeignRead], [systemA, systemB], {
        ontology: {
          ...validOntology,
          objectTypes: {
            ...validOntology.objectTypes,
            [crmDealOntologyId]: {
              id: crmDealOntologyId,
              label: 'Deal'
            }
          }
        },
        topology: {
          version: 1,
          relationships: {
            'lead-import-reads-crm-deal': {
              from: { kind: 'resource', id: 'lead-import' },
              kind: 'uses',
              to: { kind: 'ontology', id: crmDealOntologyId },
              metadata: {
                systemInterfaceGrant: {
                  consumer: { systemPath: 'sys.lead-gen', interfaceKey: 'api' },
                  provider: { systemPath: 'sys.crm', interfaceKey: 'api' },
                  resourceIds: ['lead-import'],
                  ontologyIds: [crmDealOntologyId]
                }
              }
            }
          }
        }
      }),
      { mode: 'strict' }
    )

    expect(result.valid).toBe(true)
  })

  it('continues to accept same-System and global ontology refs without topology grants', () => {
    const resourceWithSameAndGlobalRefs: ResourceEntry = {
      ...workflowResource,
      ontology: {
        actions: ['sys.lead-gen:action/import-leads'],
        primaryAction: 'sys.lead-gen:action/import-leads',
        reads: ['sys.lead-gen:object/company', 'global:object/account']
      }
    }

    const result = validateResourceGovernance(
      'test-org',
      {
        version: '1.0.0',
        workflows: [createGovernedWorkflow(resourceWithSameAndGlobalRefs)]
      },
      createModel([resourceWithSameAndGlobalRefs], [systemA], {
        ontology: {
          ...validOntology,
          objectTypes: {
            ...validOntology.objectTypes,
            'global:object/account': {
              id: 'global:object/account',
              label: 'Account'
            }
          }
        }
      }),
      { mode: 'strict' }
    )

    expect(result.valid).toBe(true)
  })

  it('reports dangling required topology refs', () => {
    expect(() =>
      validateResourceGovernance(
        'test-org',
        {
          version: '1.0.0',
          workflows: [createGovernedWorkflow()]
        },
        createModel([workflowResource], [systemA], {
          topology: {
            version: 1,
            relationships: {
              'missing-trigger-starts-import': {
                from: { kind: 'trigger', id: 'missing-trigger' },
                kind: 'triggers',
                to: { kind: 'resource', id: 'lead-import' },
                required: true
              }
            }
          }
        }),
        { mode: 'strict' }
      )
    ).toThrow("Topology relationship 'missing-trigger-starts-import' from references missing trigger 'missing-trigger'")
  })

  it('reports active OM resources that reference missing Systems', () => {
    const resourceWithMissingSystem: ResourceEntry = {
      ...workflowResource,
      systemPath: 'sys.missing'
    }

    expect(() =>
      validateResourceGovernance(
        'test-org',
        {
          version: '1.0.0',
          workflows: [createGovernedWorkflow(resourceWithMissingSystem)]
        },
        createModel([resourceWithMissingSystem], [systemA]),
        { mode: 'strict' }
      )
    ).toThrow("OM resource 'lead-import' references missing system path 'sys.missing'.")
  })

  it('reports raw runtime resourceId authoring when a descriptor is required', () => {
    expect(() =>
      validateResourceGovernance(
        'test-org',
        {
          version: '1.0.0',
          workflows: [createRawWorkflow()]
        },
        createModel(),
        { mode: 'strict' }
      )
    ).toThrow("Code-side resource 'lead-import' authors raw resourceId/type values")
  })
})

describe('validateDeclaredSystemInterfaceReadiness', () => {
  it('allows empty apiInterface markers for systems without scoped API resources', () => {
    expect(() =>
      validateDeclaredSystemInterfaceReadiness('test-org', {
        systems: {
          'sales.lead-gen': {
            id: 'sales.lead-gen',
            label: 'Lead Gen',
            order: 10,
            apiInterface: {
              lifecycle: 'active',
              readinessProfile: 'sales.lead-gen.api',
              resourceIds: []
            }
          },
          'sales.crm': {
            id: 'sales.crm',
            label: 'CRM',
            order: 20,
            apiInterface: {
              lifecycle: 'active',
              resourceIds: []
            }
          }
        }
      })
    ).not.toThrow()
  })

  it('scans flat system.apiInterface markers', () => {
    let error: unknown
    try {
      validateDeclaredSystemInterfaceReadiness('test-org', {
        systems: {
          sales: {
            id: 'sales',
            label: 'Sales',
            order: 10,
            apiInterface: {
              lifecycle: 'active',
              readinessProfile: 'sales.unknown.api'
            }
          }
        }
      })
    } catch (caught) {
      error = caught
    }

    expect(error).toBeInstanceOf(RegistryValidationError)
    expect((error as RegistryValidationError).field).toBe('systems.sales.apiInterface.readinessProfile')
  })
})

describe('ResourceRegistry - ExecutionInterface validation integration', () => {
  it('should throw on registry construction when interface misses required field', () => {
    const workflow: WorkflowDefinition = {
      config: {
        resourceId: 'test-workflow',
        name: 'Test Workflow',
        description: 'Test',
        version: '1.0.0',
        type: 'workflow',
        status: 'dev'
      },
      contract: {
        inputSchema: z.object({
          taskName: z.string(),
          priority: z.string()
        }),
        outputSchema: z.object({})
      },
      steps: {},
      entryPoint: 'start',
      interface: {
        form: {
          fields: [
            { name: 'taskName', label: 'Task Name', type: 'text', required: true }
            // Missing 'priority' field
          ]
        }
      }
    }

    const registry: OrganizationRegistry = {
      'test-org': {
        workflows: [workflow]
      }
    }

    expect(() => {
      new ResourceRegistry(registry)
    }).toThrow('missing required field "priority"')
  })

  it('should throw when agent interface misses required field', () => {
    const agent: AgentDefinition = {
      config: {
        resourceId: 'test-agent',
        name: 'Test Agent',
        description: 'Test',
        version: '1.0.0',
        type: 'agent',
        status: 'dev',
        systemPrompt: 'You are a test agent'
      },
      contract: {
        inputSchema: z.object({
          task: z.string(),
          context: z.string()
        }),
        outputSchema: z.object({})
      },
      tools: [],
      modelConfig: {
        provider: 'mock',
        model: 'mock',
        apiKey: 'test'
      },
      interface: {
        form: {
          fields: [
            { name: 'task', label: 'Task', type: 'textarea', required: true }
            // Missing 'context' field
          ]
        }
      }
    }

    const registry: OrganizationRegistry = {
      'test-org': {
        agents: [agent]
      }
    }

    expect(() => {
      new ResourceRegistry(registry)
    }).toThrow('missing required field "context"')
  })

  it('should pass when interface correctly matches schema', () => {
    const workflow: WorkflowDefinition = {
      config: {
        resourceId: 'valid-workflow',
        name: 'Valid Workflow',
        description: 'Test',
        version: '1.0.0',
        type: 'workflow',
        status: 'dev'
      },
      contract: {
        inputSchema: z.object({
          taskName: z.string(),
          priority: z.enum(['low', 'medium', 'high']),
          description: z.string().optional()
        }),
        outputSchema: z.object({})
      },
      steps: {},
      entryPoint: 'start',
      interface: {
        form: {
          title: 'Run Workflow',
          fields: [
            { name: 'taskName', label: 'Task Name', type: 'text', required: true },
            { name: 'priority', label: 'Priority', type: 'select', required: true },
            { name: 'description', label: 'Description', type: 'textarea', required: false }
          ]
        }
      }
    }

    const registry: OrganizationRegistry = {
      'test-org': {
        workflows: [workflow]
      }
    }

    expect(() => {
      new ResourceRegistry(registry)
    }).not.toThrow()
  })

  it('should pass when workflow has no interface (optional)', () => {
    const workflow: WorkflowDefinition = {
      config: {
        resourceId: 'no-interface-workflow',
        name: 'No Interface Workflow',
        description: 'Test',
        version: '1.0.0',
        type: 'workflow',
        status: 'dev'
      },
      contract: {
        inputSchema: z.object({ data: z.string() }),
        outputSchema: z.object({})
      },
      steps: {},
      entryPoint: 'start'
      // No interface - should pass
    }

    const registry: OrganizationRegistry = {
      'test-org': {
        workflows: [workflow]
      }
    }

    expect(() => {
      new ResourceRegistry(registry)
    }).not.toThrow()
  })
})

describe('Relationship Validation', () => {
  // Helper to create minimal mock resources
  const createMockWorkflow = (resourceId: string): WorkflowDefinition => ({
    config: {
      resourceId,
      name: `Workflow ${resourceId}`,
      description: `Test workflow ${resourceId}`,
      version: '1.0.0',
      type: 'workflow',
      status: 'dev'
    },
    contract: {
      inputSchema: z.object({ data: z.string() }),
      outputSchema: z.object({ result: z.boolean() })
    },
    steps: {},
    entryPoint: 'start'
  })

  const createMockAgent = (resourceId: string): AgentDefinition => ({
    config: {
      resourceId,
      name: `Agent ${resourceId}`,
      description: `Test agent ${resourceId}`,
      version: '1.0.0',
      type: 'agent',
      status: 'dev',
      systemPrompt: 'You are a test agent'
    },
    contract: {
      inputSchema: z.object({ query: z.string() }),
      outputSchema: z.object({ response: z.string() })
    },
    tools: [],
    modelConfig: {
      provider: 'mock',
      model: 'mock',
      apiKey: 'test-key'
    }
  })

  describe('validateTriggers (new field names)', () => {
    it('validates trigger.triggers.agents exist (using resourceId)', () => {
      const agent = createMockAgent('test-agent')
      const registry: OrganizationRegistry = {
        'test-org': {
          agents: [agent],
          triggers: [
            {
              resourceId: 'test-trigger',
              type: 'trigger',
              triggerType: 'webhook',
              name: 'Test Trigger',
              description: 'Test trigger',
              version: '1.0.0',
              status: 'dev',
              triggers: { agents: ['test-agent'] }
            }
          ]
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).not.toThrow()
    })

    it('validates trigger.triggers.workflows exist (using resourceId)', () => {
      const workflow = createMockWorkflow('test-workflow')
      const registry: OrganizationRegistry = {
        'test-org': {
          workflows: [workflow],
          triggers: [
            {
              resourceId: 'test-trigger',
              type: 'trigger',
              triggerType: 'schedule',
              name: 'Test Trigger',
              description: 'Test trigger',
              version: '1.0.0',
              status: 'dev',
              triggers: { workflows: ['test-workflow'] }
            }
          ]
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).not.toThrow()
    })

    it('throws for trigger triggering non-existent agent (via relationships)', () => {
      // Note: Trigger relationships are now declared via ResourceRelationships
      const registry: OrganizationRegistry = {
        'test-org': {
          triggers: [
            {
              resourceId: 'test-trigger',
              type: 'trigger',
              triggerType: 'webhook',
              name: 'Test Trigger',
              description: 'Test trigger',
              version: '1.0.0',
              status: 'dev'
            }
          ],
          relationships: {
            'test-trigger': {
              triggers: { agents: ['non-existent-agent'] }
            }
          }
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).toThrow('non-existent-agent')
    })

    it('throws for trigger triggering non-existent workflow (via relationships)', () => {
      // Note: Trigger relationships are now declared via ResourceRelationships
      const registry: OrganizationRegistry = {
        'test-org': {
          triggers: [
            {
              resourceId: 'test-trigger',
              type: 'trigger',
              triggerType: 'event',
              name: 'Test Trigger',
              description: 'Test trigger',
              version: '1.0.0',
              status: 'dev'
            }
          ],
          relationships: {
            'test-trigger': {
              triggers: { workflows: ['non-existent-workflow'] }
            }
          }
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).toThrow('non-existent-workflow')
    })

    it('allows trigger with no relationships (triggers are metadata only)', () => {
      // Note: Triggers without relationships are now valid - they're just metadata
      // The trigger's targets are defined in ResourceRelationships, not on the trigger itself
      const registry: OrganizationRegistry = {
        'test-org': {
          triggers: [
            {
              resourceId: 'test-trigger',
              type: 'trigger',
              triggerType: 'webhook',
              name: 'Test Trigger',
              description: 'Test trigger',
              version: '1.0.0',
              status: 'dev'
            }
          ]
          // No relationships - trigger is just metadata
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).not.toThrow()
    })

    it('allows trigger with empty relationships object', () => {
      // Note: Empty relationships are valid - triggers without targets are allowed
      const registry: OrganizationRegistry = {
        'test-org': {
          triggers: [
            {
              resourceId: 'test-trigger',
              type: 'trigger',
              triggerType: 'manual',
              name: 'Test Trigger',
              description: 'Test trigger',
              version: '1.0.0',
              status: 'dev'
            }
          ],
          relationships: {}
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).not.toThrow()
    })
  })

  describe('validateResourceRelationships', () => {
    it('validates resource.triggers.agents exist', () => {
      const agent1 = createMockAgent('agent-1')
      const agent2 = createMockAgent('agent-2')
      const registry: OrganizationRegistry = {
        'test-org': {
          agents: [agent1, agent2],
          relationships: {
            'agent-1': {
              triggers: { agents: ['agent-2'] }
            }
          }
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).not.toThrow()
    })

    it('validates resource.triggers.workflows exist', () => {
      const agent = createMockAgent('test-agent')
      const workflow = createMockWorkflow('test-workflow')
      const registry: OrganizationRegistry = {
        'test-org': {
          agents: [agent],
          workflows: [workflow],
          relationships: {
            'test-agent': {
              triggers: { workflows: ['test-workflow'] }
            }
          }
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).not.toThrow()
    })

    it('validates resource.uses.integrations exist', () => {
      const workflow = createMockWorkflow('test-workflow')
      const registry: OrganizationRegistry = {
        'test-org': {
          workflows: [workflow],
          integrations: [
            {
              resourceId: 'integration-shopify',
              type: 'integration',
              provider: 'custom',
              credentialName: 'shopify-prod',
              name: 'Shopify Integration',
              description: 'E-commerce integration',
              version: '1.0.0',
              status: 'prod'
            }
          ],
          relationships: {
            'test-workflow': {
              uses: { integrations: ['integration-shopify'] }
            }
          }
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).not.toThrow()
    })

    it('throws for relationship declared for non-existent resource', () => {
      const registry: OrganizationRegistry = {
        'test-org': {
          relationships: {
            'non-existent-resource': {
              triggers: { agents: ['some-agent'] }
            }
          }
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).toThrow('[test-org] Relationship declared for non-existent resource: non-existent-resource')
    })

    it('throws for relationship triggering non-existent agent', () => {
      const workflow = createMockWorkflow('test-workflow')
      const registry: OrganizationRegistry = {
        'test-org': {
          workflows: [workflow],
          relationships: {
            'test-workflow': {
              triggers: { agents: ['non-existent-agent'] }
            }
          }
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).toThrow("[test-org] Resource 'test-workflow' triggers non-existent agent: non-existent-agent")
    })

    it('throws for relationship using non-existent integration', () => {
      const agent = createMockAgent('test-agent')
      const registry: OrganizationRegistry = {
        'test-org': {
          agents: [agent],
          relationships: {
            'test-agent': {
              uses: { integrations: ['non-existent-integration'] }
            }
          }
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).toThrow("[test-org] Resource 'test-agent' uses non-existent integration: non-existent-integration")
    })
  })

  describe('validateExternalResources (forward-only)', () => {
    it('validates external.triggers.agents exist', () => {
      const agent = createMockAgent('test-agent')
      const registry: OrganizationRegistry = {
        'test-org': {
          agents: [agent],
          externalResources: [
            {
              resourceId: 'external-n8n-workflow',
              type: 'external',
              platform: 'n8n',
              name: 'N8N Workflow',
              description: 'External automation',
              version: '1.0.0',
              status: 'prod',
              triggers: { agents: ['test-agent'] }
            }
          ]
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).not.toThrow()
    })

    it('validates external.triggers.workflows exist', () => {
      const workflow = createMockWorkflow('test-workflow')
      const registry: OrganizationRegistry = {
        'test-org': {
          workflows: [workflow],
          externalResources: [
            {
              resourceId: 'external-zapier-zap',
              type: 'external',
              platform: 'zapier',
              name: 'Zapier Zap',
              description: 'External automation',
              version: '1.0.0',
              status: 'prod',
              triggers: { workflows: ['test-workflow'] }
            }
          ]
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).not.toThrow()
    })

    it('validates external.uses.integrations exist', () => {
      const registry: OrganizationRegistry = {
        'test-org': {
          integrations: [
            {
              resourceId: 'integration-slack',
              type: 'integration',
              provider: 'custom',
              credentialName: 'slack-prod',
              name: 'Slack Integration',
              description: 'Chat integration',
              version: '1.0.0',
              status: 'prod'
            }
          ],
          externalResources: [
            {
              resourceId: 'external-make-scenario',
              type: 'external',
              platform: 'make',
              name: 'Make Scenario',
              description: 'External automation',
              version: '1.0.0',
              status: 'prod',
              uses: { integrations: ['integration-slack'] }
            }
          ]
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).not.toThrow()
    })

    it('validates external resourceId does not conflict with internal resources', () => {
      const workflow = createMockWorkflow('conflicting-id')
      const registry: OrganizationRegistry = {
        'test-org': {
          workflows: [workflow],
          externalResources: [
            {
              resourceId: 'conflicting-id',
              type: 'external',
              platform: 'other',
              name: 'Conflicting External',
              description: 'Should conflict',
              version: '1.0.0',
              status: 'dev'
            }
          ]
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).toThrow("[test-org] External resource ID 'conflicting-id' conflicts with internal resource ID")
    })

    it('does NOT validate triggeredBy (field removed)', () => {
      // This test ensures we don't validate triggeredBy field (it's been removed)
      const workflow = createMockWorkflow('test-workflow')
      const registry: OrganizationRegistry = {
        'test-org': {
          workflows: [workflow],
          externalResources: [
            {
              resourceId: 'external-resource',
              type: 'external',
              platform: 'n8n',
              name: 'External Resource',
              description: 'Has no triggeredBy field',
              version: '1.0.0',
              status: 'prod',
              triggers: { workflows: ['test-workflow'] }
              // No triggeredBy field - this is correct and should pass
            }
          ]
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).not.toThrow()
    })

    it('throws for external resource with conflicting ID', () => {
      const agent = createMockAgent('duplicate-id')
      const registry: OrganizationRegistry = {
        'test-org': {
          agents: [agent],
          externalResources: [
            {
              resourceId: 'duplicate-id',
              type: 'external',
              platform: 'n8n',
              name: 'Duplicate ID',
              description: 'Conflicts with agent',
              version: '1.0.0',
              status: 'dev'
            }
          ]
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).toThrow("[test-org] External resource ID 'duplicate-id' conflicts with internal resource ID")
    })
  })

  describe('validateHumanCheckpoints', () => {
    it('validates requestedBy.agents exist', () => {
      const agent = createMockAgent('test-agent')
      const registry: OrganizationRegistry = {
        'test-org': {
          agents: [agent],
          humanCheckpoints: [
            {
              resourceId: 'approval-queue',
              type: 'human',
              name: 'Approval Queue',
              description: 'Human approval checkpoint',
              version: '1.0.0',
              status: 'prod',
              requestedBy: { agents: ['test-agent'] }
            }
          ]
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).not.toThrow()
    })

    it('validates requestedBy.workflows exist', () => {
      const workflow = createMockWorkflow('test-workflow')
      const registry: OrganizationRegistry = {
        'test-org': {
          workflows: [workflow],
          humanCheckpoints: [
            {
              resourceId: 'review-queue',
              type: 'human',
              name: 'Review Queue',
              description: 'Human review checkpoint',
              version: '1.0.0',
              status: 'prod',
              requestedBy: { workflows: ['test-workflow'] }
            }
          ]
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).not.toThrow()
    })

    it('validates routesTo.agents exist', () => {
      const agent = createMockAgent('fulfillment-agent')
      const registry: OrganizationRegistry = {
        'test-org': {
          agents: [agent],
          humanCheckpoints: [
            {
              resourceId: 'approval-queue',
              type: 'human',
              name: 'Approval Queue',
              description: 'Human approval checkpoint',
              version: '1.0.0',
              status: 'prod',
              routesTo: { agents: ['fulfillment-agent'] }
            }
          ]
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).not.toThrow()
    })

    it('validates routesTo.workflows exist', () => {
      const workflow = createMockWorkflow('fulfillment-workflow')
      const registry: OrganizationRegistry = {
        'test-org': {
          workflows: [workflow],
          humanCheckpoints: [
            {
              resourceId: 'approval-queue',
              type: 'human',
              name: 'Approval Queue',
              description: 'Human approval checkpoint',
              version: '1.0.0',
              status: 'prod',
              routesTo: { workflows: ['fulfillment-workflow'] }
            }
          ]
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).not.toThrow()
    })

    it('throws for requestedBy referencing non-existent agent', () => {
      const registry: OrganizationRegistry = {
        'test-org': {
          humanCheckpoints: [
            {
              resourceId: 'approval-queue',
              type: 'human',
              name: 'Approval Queue',
              description: 'Human approval checkpoint',
              version: '1.0.0',
              status: 'prod',
              requestedBy: { agents: ['non-existent-agent'] }
            }
          ]
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).toThrow("[test-org] Human checkpoint 'approval-queue' requestedBy non-existent agent: non-existent-agent")
    })

    it('throws for routesTo referencing non-existent workflow', () => {
      const registry: OrganizationRegistry = {
        'test-org': {
          humanCheckpoints: [
            {
              resourceId: 'approval-queue',
              type: 'human',
              name: 'Approval Queue',
              description: 'Human approval checkpoint',
              version: '1.0.0',
              status: 'prod',
              routesTo: { workflows: ['non-existent-workflow'] }
            }
          ]
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).toThrow("[test-org] Human checkpoint 'approval-queue' routesTo non-existent workflow: non-existent-workflow")
    })

    it('throws for human checkpoint with conflicting ID', () => {
      const workflow = createMockWorkflow('conflicting-id')
      const registry: OrganizationRegistry = {
        'test-org': {
          workflows: [workflow],
          humanCheckpoints: [
            {
              resourceId: 'conflicting-id',
              type: 'human',
              name: 'Conflicting Checkpoint',
              description: 'Conflicts with workflow',
              version: '1.0.0',
              status: 'dev'
            }
          ]
        }
      }

      expect(() => {
        new ResourceRegistry(registry)
      }).toThrow("[test-org] Human checkpoint ID 'conflicting-id' conflicts with internal resource ID")
    })
  })
})

describe('detectMissingApiInterfaceDeclarations', () => {
  const makeSystem = (id: string, apiInterface?: object) => ({
    id,
    title: id,
    description: `System ${id}`,
    kind: 'operational' as const,
    governedByKnowledge: [],
    drivesGoals: [],
    status: 'active' as const,
    order: 10,
    ...(apiInterface !== undefined ? { apiInterface } : {})
  })

  const makeResource = (id: string, systemPath: string, ontology?: object): ResourceEntry => ({
    id,
    kind: 'workflow' as const,
    systemPath,
    status: 'active' as const,
    order: 10,
    ...(ontology !== undefined ? { ontology } : {})
  })

  it('returns [] when organizationModel is undefined', () => {
    const result = detectMissingApiInterfaceDeclarations('test-org', undefined)
    expect(result).toEqual([])
  })

  it('returns [] when a system declares apiInterface and has a matching resource with ontology bindings', () => {
    const model = {
      systems: {
        'sales.lead-gen': makeSystem('sales.lead-gen', {
          lifecycle: 'active',
          readinessProfile: 'sales.lead-gen.api'
        })
      },
      resources: {
        'lead-import': makeResource('lead-import', 'sales.lead-gen', {
          actions: ['sales.lead-gen:action/import-leads'],
          writes: ['sales.lead-gen:object/lead']
        })
      }
    }

    const result = detectMissingApiInterfaceDeclarations('test-org', model as never)
    expect(result).toEqual([])
  })

  it('returns one gap when a system lacks apiInterface and has a matching resource with non-empty ontology', () => {
    const model = {
      systems: {
        'sales.lead-gen': makeSystem('sales.lead-gen')
      },
      resources: {
        'lead-import': makeResource('lead-import', 'sales.lead-gen', {
          actions: ['sales.lead-gen:action/import-leads'],
          writes: ['sales.lead-gen:object/lead']
        })
      }
    }

    const result = detectMissingApiInterfaceDeclarations('test-org', model as never)
    expect(result).toHaveLength(1)
    expect(result[0].systemPath).toBe('sales.lead-gen')
    expect(result[0].resourceIds).toEqual(['lead-import'])
    expect(result[0].message).toContain('sales.lead-gen')
    expect(result[0].message).toContain('lead-import')
  })

  it('returns [] when a system lacks apiInterface but its matching resource has empty ontology (no actions or writes)', () => {
    const model = {
      systems: {
        'sales.lead-gen': makeSystem('sales.lead-gen')
      },
      resources: {
        'lead-import': makeResource('lead-import', 'sales.lead-gen', {
          reads: ['sales.lead-gen:object/company']
        })
      }
    }

    const result = detectMissingApiInterfaceDeclarations('test-org', model as never)
    expect(result).toEqual([])
  })
})
