import { describe, expect, it } from 'vitest'
import { ZodError } from 'zod'
import { bindResourceDescriptor } from '../../../platform/registry/types'
import {
  ContractRefSchema,
  DEFAULT_ORGANIZATION_MODEL_RESOURCES,
  EventIdSchema,
  ResourceEntrySchema,
  ResourceKindSchema,
  ResourceOntologyBindingSchema,
  ResourcesDomainSchema,
  defineResource,
  defineResources,
  parseContractRef
} from '../../domains/resources'
import { resolveOrganizationModel } from '../../resolve'

const VALID_SYSTEM = {
  id: 'sys.lead-gen',
  order: 10,
  title: 'Lead Generation Pipeline',
  description: 'Coordinates prospecting, enrichment, qualification, and outreach preparation.',
  kind: 'operational' as const,
  status: 'active' as const
}

const VALID_ROLE = {
  id: 'role.sales-ops',
  order: 10,
  title: 'Sales Ops'
}

const WORKFLOW_RESOURCE = {
  id: 'LGN-01-company-scrape',
  order: 10,
  kind: 'workflow' as const,
  systemPath: 'sys.lead-gen',
  title: 'Company Scrape',
  description: 'Scrapes company data for lead generation.',
  ownerRoleId: 'role.sales-ops',
  status: 'active' as const,
  codeRefs: [
    {
      path: 'operations/src/lead-gen/company-scrape/index.ts',
      role: 'entrypoint' as const,
      symbol: 'companyScrapeWorkflow',
      description: 'Workflow definition bound to this OM resource descriptor.'
    }
  ]
}

const WORKFLOW_EVENT_EMISSION = {
  eventKey: 'processed',
  label: 'Processed',
  payloadSchema: 'leadgen.company.processed',
  lifecycle: 'active' as const
}

const AGENT_RESOURCE = {
  id: 'command-center-assistant',
  order: 20,
  kind: 'agent' as const,
  systemPath: 'sys.lead-gen',
  ownerRoleId: 'role.sales-ops',
  status: 'active' as const,
  agentKind: 'platform' as const,
  actsAsRoleId: 'role.sales-ops',
  sessionCapable: true,
  invocations: [{ kind: 'slash-command' as const, command: '/work handoff' }]
}

const INTEGRATION_RESOURCE = {
  id: 'attio-crm',
  order: 30,
  kind: 'integration' as const,
  systemPath: 'sys.lead-gen',
  ownerRoleId: 'role.sales-ops',
  status: 'active' as const,
  provider: 'attio'
}

const SCRIPT_RESOURCE = {
  id: 'leadgen-backfill-script',
  order: 40,
  kind: 'script' as const,
  systemPath: 'sys.lead-gen',
  ownerRoleId: 'role.sales-ops',
  status: 'active' as const,
  language: 'typescript' as const,
  source: {
    file: 'scripts/leadgen/backfill.ts'
  }
}

function resolveWithResources(resources: Record<string, unknown>) {
  return resolveOrganizationModel({
    roles: { 'role.sales-ops': VALID_ROLE },
    systems: { 'sys.lead-gen': VALID_SYSTEM },
    resources: resources as Record<string, typeof WORKFLOW_RESOURCE>
  })
}

describe('ResourceEntrySchema', () => {
  it('accepts workflow, agent, integration, and script descriptors with required systemPath', () => {
    expect(ResourceEntrySchema.safeParse(WORKFLOW_RESOURCE).success).toBe(true)
    expect(ResourceEntrySchema.safeParse(AGENT_RESOURCE).success).toBe(true)
    expect(ResourceEntrySchema.safeParse(INTEGRATION_RESOURCE).success).toBe(true)
    expect(ResourceEntrySchema.safeParse(SCRIPT_RESOURCE).success).toBe(true)
  })

  it('rejects resources without a systemPath', () => {
    const { systemPath: _systemPath, ...resourceWithoutSystem } = WORKFLOW_RESOURCE

    expect(ResourceEntrySchema.safeParse(resourceWithoutSystem).success).toBe(false)
  })

  it('defaults codeRefs and accepts resource-level implementation breadcrumbs', () => {
    const withCodeRefs = ResourceEntrySchema.safeParse(WORKFLOW_RESOURCE)
    const withoutCodeRefs = ResourceEntrySchema.safeParse({
      ...WORKFLOW_RESOURCE,
      codeRefs: undefined
    })

    expect(withCodeRefs.success).toBe(true)
    expect(withoutCodeRefs.success).toBe(true)

    if (withCodeRefs.success) {
      expect(withCodeRefs.data.codeRefs).toEqual(WORKFLOW_RESOURCE.codeRefs)
    }
    if (withoutCodeRefs.success) {
      expect(withoutCodeRefs.data.codeRefs).toEqual([])
    }
  })

  it('rejects codeRefs with an empty path', () => {
    expect(
      ResourceEntrySchema.safeParse({
        ...WORKFLOW_RESOURCE,
        codeRefs: [
          {
            path: '   ',
            role: 'entrypoint'
          }
        ]
      }).success
    ).toBe(false)
  })

  it('rejects codeRefs with an invalid role', () => {
    expect(
      ResourceEntrySchema.safeParse({
        ...WORKFLOW_RESOURCE,
        codeRefs: [
          {
            path: 'operations/src/lead-gen/company-scrape/index.ts',
            role: 'runtime'
          }
        ]
      }).success
    ).toBe(false)
  })

  it('rejects multi-system membership', () => {
    expect(
      ResourceEntrySchema.safeParse({
        ...WORKFLOW_RESOURCE,
        systemPath: ['sys.lead-gen', 'sys.crm']
      }).success
    ).toBe(false)
  })

  it('rejects an agent kind outside the code-side mirror enum', () => {
    expect(
      ResourceEntrySchema.safeParse({
        ...AGENT_RESOURCE,
        agentKind: 'runner'
      }).success
    ).toBe(false)
  })

  it('defaults agent invocations and accepts slash-command orchestration entries', () => {
    const result = ResourceEntrySchema.safeParse({
      ...AGENT_RESOURCE,
      invocations: undefined
    })

    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.kind).toBe('agent')
      if (result.data.kind === 'agent') {
        expect(result.data.invocations).toEqual([])
      }
    }

    expect(ResourceEntrySchema.safeParse(AGENT_RESOURCE).success).toBe(true)
  })

  it('rejects malformed agent invocation entries', () => {
    expect(
      ResourceEntrySchema.safeParse({
        ...AGENT_RESOURCE,
        invocations: [{ kind: 'slash-command', command: 'work handoff' }]
      }).success
    ).toBe(false)
  })

  it('accepts event emissions on workflow and agent resources only', () => {
    const workflowResult = ResourceEntrySchema.safeParse({
      ...WORKFLOW_RESOURCE,
      emits: [WORKFLOW_EVENT_EMISSION]
    })
    const agentResult = ResourceEntrySchema.safeParse({
      ...AGENT_RESOURCE,
      emits: [{ eventKey: 'approved', label: 'Approved' }]
    })
    const integrationResult = ResourceEntrySchema.safeParse({
      ...INTEGRATION_RESOURCE,
      emits: [{ eventKey: 'received', label: 'Received' }]
    })

    expect(workflowResult.success).toBe(true)
    expect(agentResult.success).toBe(true)
    expect(integrationResult.success).toBe(true)
    if (workflowResult.success && workflowResult.data.kind === 'workflow') {
      expect(workflowResult.data.emits).toEqual([WORKFLOW_EVENT_EMISSION])
    }
    if (agentResult.success && agentResult.data.kind === 'agent') {
      expect(agentResult.data.emits).toEqual([{ eventKey: 'approved', label: 'Approved' }])
    }
    if (integrationResult.success && integrationResult.data.kind === 'integration') {
      expect('emits' in integrationResult.data).toBe(false)
    }
  })

  it('accepts optional ontology bindings on resource variants without changing top-level event emissions', () => {
    const ontology = {
      actions: ['sys.lead-gen:action/company.scrape'],
      primaryAction: 'sys.lead-gen:action/company.scrape',
      reads: ['sys.lead-gen:object/company'],
      writes: ['sys.lead-gen:object/company'],
      usesCatalogs: ['sys.lead-gen:catalog/build-template'],
      emits: ['sys.lead-gen:event/company.scraped']
    }
    const workflowResult = ResourceEntrySchema.safeParse({
      ...WORKFLOW_RESOURCE,
      ontology,
      emits: [WORKFLOW_EVENT_EMISSION]
    })
    const agentResult = ResourceEntrySchema.safeParse({
      ...AGENT_RESOURCE,
      ontology
    })
    const integrationResult = ResourceEntrySchema.safeParse({
      ...INTEGRATION_RESOURCE,
      ontology
    })
    const scriptResult = ResourceEntrySchema.safeParse({
      ...SCRIPT_RESOURCE,
      ontology
    })

    expect(workflowResult.success).toBe(true)
    expect(agentResult.success).toBe(true)
    expect(integrationResult.success).toBe(true)
    expect(scriptResult.success).toBe(true)
    if (workflowResult.success && workflowResult.data.kind === 'workflow') {
      expect(workflowResult.data.ontology).toEqual(ontology)
      expect(workflowResult.data.emits).toEqual([WORKFLOW_EVENT_EMISSION])
    }
  })

  it('rejects malformed ontology binding ids', () => {
    expect(
      ResourceOntologyBindingSchema.safeParse({
        actions: ['sys.lead-gen/action/company.scrape']
      }).success
    ).toBe(false)
  })

  it('rejects primaryAction values outside actions', () => {
    expect(
      ResourceOntologyBindingSchema.safeParse({
        actions: ['sys.lead-gen:action/company.scrape'],
        primaryAction: 'sys.lead-gen:action/company.qualify'
      }).success
    ).toBe(false)
  })

  it('validates derived event id format', () => {
    expect(EventIdSchema.parse('crm.deal:won')).toBe('crm.deal:won')
    expect(EventIdSchema.parse('LGN-01-company-scrape:processed')).toBe('LGN-01-company-scrape:processed')
    expect(() => EventIdSchema.parse('crm.deal.won')).toThrow()
  })
})

describe('ResourcesDomainSchema', () => {
  it('defaults resources to an empty record when omitted', () => {
    const result = ResourcesDomainSchema.safeParse({})

    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data).toEqual(DEFAULT_ORGANIZATION_MODEL_RESOURCES)
    }
  })

  it.each(['workflow', 'agent', 'integration', 'script'] as const)('accepts kind "%s"', (kind) => {
    expect(ResourceKindSchema.safeParse(kind).success).toBe(true)
  })
})

describe('resolveOrganizationModel - resources domain integration', () => {
  it('accepts valid resources with system and role references', () => {
    const model = resolveWithResources({
      'LGN-01-company-scrape': WORKFLOW_RESOURCE,
      'command-center-assistant': AGENT_RESOURCE,
      'attio-crm': INTEGRATION_RESOURCE,
      'leadgen-backfill-script': SCRIPT_RESOURCE
    })

    expect(Object.keys(model.resources)).toHaveLength(4)
  })

  it('throws when a resource entry id does not match its map key', () => {
    expect(() =>
      resolveWithResources({
        'LGN-01-company-scrape': WORKFLOW_RESOURCE,
        'wrong-key': {
          ...WORKFLOW_RESOURCE,
          id: 'LGN-01-company-scrape'
        }
      })
    ).toThrow()
  })

  it('throws when systemPath references an unknown system', () => {
    expect(() =>
      resolveWithResources({
        'LGN-01-company-scrape': {
          ...WORKFLOW_RESOURCE,
          systemPath: 'sys.missing'
        }
      })
    ).toThrow(/unknown system path \\"sys\.missing\\"/)
  })

  it('reports a dangling resource systemPath on the resource systemPath issue path', () => {
    let error: unknown

    try {
      resolveWithResources({
        'LGN-01-company-scrape': {
          ...WORKFLOW_RESOURCE,
          systemPath: 'sys.missing'
        }
      })
    } catch (caught) {
      error = caught
    }

    expect(error).toBeInstanceOf(ZodError)
    if (!(error instanceof ZodError)) {
      throw new Error('Expected resolveOrganizationModel to throw a ZodError')
    }

    expect(error.issues).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          path: ['resources', 'LGN-01-company-scrape', 'systemPath']
        })
      ])
    )
  })

  it('throws when ownerRoleId references an unknown role', () => {
    expect(() =>
      resolveWithResources({
        'LGN-01-company-scrape': {
          ...WORKFLOW_RESOURCE,
          ownerRoleId: 'role.missing'
        }
      })
    ).toThrow(/unknown ownerRoleId \\"role\.missing\\"/)
  })

  it('throws when agent actsAsRoleId references an unknown role', () => {
    expect(() =>
      resolveWithResources({
        'command-center-assistant': {
          ...AGENT_RESOURCE,
          actsAsRoleId: 'role.missing'
        }
      })
    ).toThrow(/unknown actsAsRoleId \\"role\.missing\\"/)
  })
})

describe('descriptor helper contract', () => {
  it('preserves typed descriptor exports through defineResource and defineResources', () => {
    const workflow = defineResource(WORKFLOW_RESOURCE)
    const resources = defineResources({
      companyScrape: WORKFLOW_RESOURCE,
      commandCenterAssistant: AGENT_RESOURCE
    })

    expect(workflow.id).toBe('LGN-01-company-scrape')
    expect(resources.commandCenterAssistant.agentKind).toBe('platform')
  })

  it('derives runtime resourceId and type from the descriptor', () => {
    const bound = bindResourceDescriptor({
      resource: WORKFLOW_RESOURCE,
      name: 'Company Scrape',
      description: 'Scrapes company data for lead generation.',
      version: '1.0.0',
      status: 'prod'
    })

    expect(bound.resourceId).toBe('LGN-01-company-scrape')
    expect(bound.type).toBe('workflow')
    expect(bound.resource).toBe(WORKFLOW_RESOURCE)
  })
})

describe('ContractRefSchema — ref string shape', () => {
  it('accepts valid package/subpath#ExportName refs', () => {
    expect(ContractRefSchema.safeParse('@repo/elevasis-core/contracts/apollo-import#inputSchema').success).toBe(true)
    expect(
      ContractRefSchema.safeParse('@repo/elevasis-core/contracts/dtc-subscription-score#outputSchema').success
    ).toBe(true)
    expect(ContractRefSchema.safeParse('my-package/sub/path#MySchema').success).toBe(true)
    expect(ContractRefSchema.safeParse('pkg#schema').success).toBe(true)
  })

  it('rejects refs without a #ExportName suffix', () => {
    expect(ContractRefSchema.safeParse('@repo/elevasis-core/contracts/apollo-import').success).toBe(false)
    expect(ContractRefSchema.safeParse('pkg/sub').success).toBe(false)
  })

  it('rejects refs with an empty export name after #', () => {
    expect(ContractRefSchema.safeParse('@repo/elevasis-core/contracts/apollo-import#').success).toBe(false)
  })

  it('rejects empty strings', () => {
    expect(ContractRefSchema.safeParse('').success).toBe(false)
  })
})

describe('parseContractRef — browser-safe parser', () => {
  it('parses a valid ref into moduleSpecifier and exportName', () => {
    const result = parseContractRef('@repo/elevasis-core/contracts/apollo-import#inputSchema')
    expect(result.moduleSpecifier).toBe('@repo/elevasis-core/contracts/apollo-import')
    expect(result.exportName).toBe('inputSchema')
  })

  it('uses lastIndexOf so a # in the module specifier (unusual but valid) is handled correctly', () => {
    const result = parseContractRef('pkg/sub#Export#actualExport')
    expect(result.moduleSpecifier).toBe('pkg/sub#Export')
    expect(result.exportName).toBe('actualExport')
  })

  it('throws on a ref without #', () => {
    expect(() => parseContractRef('pkg/no-hash')).toThrow(/missing the required/)
  })

  it('throws on a ref with empty export name', () => {
    expect(() => parseContractRef('pkg/sub#')).toThrow(/empty export name/)
  })
})

describe('ResourceOntologyBindingSchema — contract field', () => {
  it('accepts a binding without a contract field (backward compat)', () => {
    expect(
      ResourceOntologyBindingSchema.safeParse({
        actions: ['sys.lead-gen:action/company.scrape']
      }).success
    ).toBe(true)
  })

  it('accepts a binding with a valid contract.input ref', () => {
    expect(
      ResourceOntologyBindingSchema.safeParse({
        contract: {
          input: '@repo/elevasis-core/contracts/apollo-import#inputSchema'
        }
      }).success
    ).toBe(true)
  })

  it('accepts a binding with both contract.input and contract.output refs', () => {
    expect(
      ResourceOntologyBindingSchema.safeParse({
        contract: {
          input: '@repo/elevasis-core/contracts/dtc-subscription-score#inputSchema',
          output: '@repo/elevasis-core/contracts/dtc-subscription-score#outputSchema'
        }
      }).success
    ).toBe(true)
  })

  it('accepts a binding with an empty contract object (both sides optional)', () => {
    expect(
      ResourceOntologyBindingSchema.safeParse({
        contract: {}
      }).success
    ).toBe(true)
  })

  it('rejects a malformed contract.input ref (missing #ExportName)', () => {
    expect(
      ResourceOntologyBindingSchema.safeParse({
        contract: {
          input: '@repo/elevasis-core/contracts/apollo-import'
        }
      }).success
    ).toBe(false)
  })

  it('rejects a malformed contract.output ref (missing #ExportName)', () => {
    expect(
      ResourceOntologyBindingSchema.safeParse({
        contract: {
          output: 'not-a-valid-ref'
        }
      }).success
    ).toBe(false)
  })
})

describe('resolveOrganizationModel — contract ref Tier-1 shape validation', () => {
  it('accepts a resource with a valid contract binding', () => {
    const model = resolveWithResources({
      'LGN-01-company-scrape': {
        ...WORKFLOW_RESOURCE,
        ontology: {
          contract: {
            input: '@repo/elevasis-core/contracts/apollo-import#inputSchema'
          }
        }
      }
    })
    expect(model.resources['LGN-01-company-scrape']?.ontology?.contract?.input).toBe(
      '@repo/elevasis-core/contracts/apollo-import#inputSchema'
    )
  })

  it('rejects a resource with a malformed contract.input ref', () => {
    expect(() =>
      resolveWithResources({
        'LGN-01-company-scrape': {
          ...WORKFLOW_RESOURCE,
          ontology: {
            contract: {
              input: 'bad-ref-no-hash'
            }
          }
        }
      })
    ).toThrow()
  })

  it('leaves resources without contract binding unchanged (additive + optional)', () => {
    const model = resolveWithResources({
      'LGN-01-company-scrape': WORKFLOW_RESOURCE
    })
    expect(model.resources['LGN-01-company-scrape']?.ontology).toBeUndefined()
  })
})
