import { describe, expect, it } from 'vitest'
import { type CrmPriorityRuleConfig, type StatefulPipelineDefinition } from '../../organization-model/domains/sales'
import { SYSTEM_INTERFACE_PROFILES } from '../../organization-model/domains/systems'

// Inline fixture for lead-gen pipeline stage/state validation tests.
// The canonical constants live in @repo/elevasis-core; @repo/core cannot depend on it.
const LEAD_GEN_PIPELINE_DEFINITIONS: Record<string, StatefulPipelineDefinition[]> = {
  'acq.list-member': [
    {
      pipelineKey: 'lead-gen',
      label: 'Lead Generation',
      entityKey: 'acq.list-member',
      stages: [
        {
          stageKey: 'outreach',
          label: 'Outreach',
          states: [
            { stateKey: 'pending', label: 'Pending' },
            { stateKey: 'personalized', label: 'Personalized' },
            { stateKey: 'uploaded', label: 'Uploaded' },
            { stateKey: 'interested', label: 'Interested' }
          ]
        },
        {
          stageKey: 'prospecting',
          label: 'Prospecting',
          states: [
            { stateKey: 'pending', label: 'Pending' },
            { stateKey: 'discovered', label: 'Discovered' },
            { stateKey: 'verified', label: 'Verified' }
          ]
        },
        { stageKey: 'qualification', label: 'Qualification', states: [{ stateKey: 'pending', label: 'Pending' }] }
      ]
    }
  ],
  'acq.list-company': [
    {
      pipelineKey: 'lead-gen',
      label: 'Lead Generation',
      entityKey: 'acq.list-company',
      stages: [
        {
          stageKey: 'outreach',
          label: 'Outreach',
          states: [
            { stateKey: 'pending', label: 'Pending' },
            { stateKey: 'uploaded', label: 'Uploaded' }
          ]
        },
        {
          stageKey: 'prospecting',
          label: 'Prospecting',
          states: [
            { stateKey: 'pending', label: 'Pending' },
            { stateKey: 'populated', label: 'Populated' },
            { stateKey: 'extracted', label: 'Extracted' }
          ]
        },
        {
          stageKey: 'qualification',
          label: 'Qualification',
          states: [
            { stateKey: 'pending', label: 'Pending' },
            { stateKey: 'qualified', label: 'Qualified' }
          ]
        }
      ]
    }
  ]
}
import { DEFAULT_ORGANIZATION_MODEL } from '../../organization-model/defaults'
import type { OrganizationModel } from '../../organization-model/types'
import { CrmPriorityOverrideSchema, evaluateCrmDealPriority, resolveCrmPriorityRuleConfig } from './crm-priority'
import {
  AddCompaniesToListRequestSchema,
  AddContactsToListRequestSchema,
  AcqArtifactOwnerKindSchema,
  AcqListDetailResponseSchema,
  AcqContactResponseSchema,
  AcqContactStatusSchema,
  AcqEmailValidSchema,
  AcqListResponseSchema,
  AcqListStatusResponseSchema,
  BuildPlanSnapshotSchema,
  CreateArtifactRequestSchema,
  CreateCompanyRequestSchema,
  CreateContactRequestSchema,
  CreateDealNoteRequestSchema,
  CreateDealTaskRequestSchema,
  CrmStateKeySchema,
  CrmTransitionItemRequestSchema,
  CreateListRequestSchema,
  DealDetailResponseSchema,
  DealListItemSchema,
  DealListResponseSchema,
  DealNoteResponseSchema,
  DealStageSchema,
  DealTaskResponseSchema,
  ExecuteActionRequestSchema,
  IcpRubricSchema,
  GetListQuerySchema,
  ListArtifactsQuerySchema,
  ListCompaniesQuerySchema,
  ListContactsQuerySchema,
  ListDealsQuerySchema,
  ListDealTasksDueQuerySchema,
  ListMembersQuerySchema,
  ListReadQuerySchema,
  ListRecordsQuerySchema,
  ListStatusSchema,
  PipelineConfigSchema,
  PipelineStageSchema,
  ScrapingConfigSchema,
  TransitionDealStateRequestSchema,
  TransitionItemRequestSchema,
  UpdateCompanyRequestSchema,
  UpdateContactRequestSchema,
  UpdateListConfigRequestSchema,
  UpdateListRequestSchema,
  UpdateListStatusRequestSchema
} from './api-schemas'
import { createBuildPlanSnapshotFromTemplate } from './build-templates'
import {
  compileCrmApiOntologyValidationIndex,
  compileBusinessOntologyValidationIndex,
  computeInterfaceReadiness,
  compileLeadGenApiOntologyValidationIndex,
  compileLeadGenCrmHandoffOntologyValidationIndex,
  CRM_API_INTERFACE,
  LEAD_GEN_CRM_HANDOFF_INTERFACE,
  CRM_PIPELINE_CATALOG_ONTOLOGY_ID,
  LEAD_GEN_API_INTERFACE,
  LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID
} from './ontology-validation'

const CRM_PIPELINE_DEFINITION: StatefulPipelineDefinition = {
  pipelineKey: 'crm',
  label: 'CRM',
  entityKey: 'crm.deal',
  stages: [
    {
      stageKey: 'interested',
      label: 'Interested',
      color: 'blue',
      states: [
        { stateKey: 'discovery_replied', label: 'Discovery Replied' },
        { stateKey: 'discovery_link_sent', label: 'Discovery Link Sent' },
        { stateKey: 'discovery_nudging', label: 'Discovery Nudging' },
        { stateKey: 'discovery_booking_cancelled', label: 'Discovery Booking Cancelled' },
        { stateKey: 'reply_sent', label: 'Reply Sent' },
        { stateKey: 'followup_1_sent', label: 'Follow-up 1 Sent' },
        { stateKey: 'followup_2_sent', label: 'Follow-up 2 Sent' },
        { stateKey: 'followup_3_sent', label: 'Follow-up 3 Sent' }
      ]
    },
    { stageKey: 'proposal', label: 'Proposal', color: 'yellow', states: [] },
    { stageKey: 'closing', label: 'Closing', color: 'orange', states: [] },
    { stageKey: 'closed_won', label: 'Closed Won', color: 'green', states: [] },
    { stageKey: 'closed_lost', label: 'Closed Lost', color: 'red', states: [] },
    { stageKey: 'nurturing', label: 'Nurturing', color: 'grape', states: [] }
  ]
}

const DEFAULT_CRM_PRIORITY_RULE_CONFIG: CrmPriorityRuleConfig = {
  buckets: [
    { bucketKey: 'needs_response', label: 'Needs Response', rank: 10, color: 'red' },
    { bucketKey: 'follow_up_due', label: 'Follow-up Due', rank: 20, color: 'orange' },
    { bucketKey: 'waiting', label: 'Waiting', rank: 30, color: 'blue' },
    { bucketKey: 'stale', label: 'Stale', rank: 40, color: 'gray' },
    { bucketKey: 'closed_low', label: 'Closed', rank: 50, color: 'dark' }
  ],
  closedStageKeys: ['closed_won', 'closed_lost'],
  followUpAfterDaysByStateKey: {
    discovery_link_sent: 3,
    discovery_nudging: 2,
    reply_sent: 3,
    followup_1_sent: 3,
    followup_2_sent: 5,
    followup_3_sent: 7
  },
  staleAfterDays: 14
}

// ---------------------------------------------------------------------------
// Minimal test model fixture
// ---------------------------------------------------------------------------
// Builds OrganizationModel fixtures inline so packages/core tests have no
// dependency on @repo/elevasis-core canonical tenant data.
function buildMinimalLeadGenModel(): OrganizationModel {
  return {
    ...DEFAULT_ORGANIZATION_MODEL,
    systems: {
      sales: {
        id: 'sales',
        label: 'Sales',
        order: 60,
        systems: {
          'lead-gen': {
            id: 'sales.lead-gen',
            label: 'Lead Gen',
            order: 70,
            apiInterface: {
              lifecycle: 'active',
              readinessProfile: LEAD_GEN_API_INTERFACE.readinessProfile,
              resourceIds: ['lead-gen-api-workflow']
            },
            ontology: {
              objectTypes: {
                'sales.lead-gen:object/list': {
                  id: 'sales.lead-gen:object/list',
                  label: 'Lead List',
                  ownerSystemId: 'sales.lead-gen'
                },
                'sales.lead-gen:object/company': {
                  id: 'sales.lead-gen:object/company',
                  label: 'Lead Company',
                  ownerSystemId: 'sales.lead-gen'
                },
                'sales.lead-gen:object/contact': {
                  id: 'sales.lead-gen:object/contact',
                  label: 'Lead Contact',
                  ownerSystemId: 'sales.lead-gen'
                }
              },
              catalogTypes: {
                'sales.lead-gen:catalog/build-template': {
                  id: 'sales.lead-gen:catalog/build-template',
                  label: 'Build Templates',
                  ownerSystemId: 'sales.lead-gen',
                  kind: 'template',
                  appliesTo: 'sales.lead-gen:object/list',
                  entries: {
                    'local-services': {
                      label: 'Local Services',
                      order: 10,
                      stepCatalog: 'sales.lead-gen:catalog/template-step.local-services'
                    }
                  }
                },
                'sales.lead-gen:catalog/template-step.local-services': {
                  id: 'sales.lead-gen:catalog/template-step.local-services',
                  label: 'Local Services Steps',
                  ownerSystemId: 'sales.lead-gen',
                  kind: 'template-step',
                  appliesTo: 'sales.lead-gen:object/list',
                  entries: {
                    'source-companies': {
                      label: 'Source Companies',
                      order: 10,
                      primaryEntity: 'company',
                      outputs: ['company'],
                      stageKey: 'populated',
                      dependencyMode: 'per-record-eligibility',
                      actionKey: 'lead-gen.company.source'
                    },
                    'discover-contacts': {
                      label: 'Discover Contacts',
                      order: 20,
                      primaryEntity: 'contact',
                      outputs: ['contact'],
                      stageKey: 'discovered',
                      dependencyMode: 'per-record-eligibility',
                      actionKey: 'lead-gen.contact.discover'
                    }
                  }
                },
                'sales.lead-gen:catalog/company-stage': {
                  id: 'sales.lead-gen:catalog/company-stage',
                  label: 'Company Stages',
                  ownerSystemId: 'sales.lead-gen',
                  kind: 'stage',
                  appliesTo: 'sales.lead-gen:object/company',
                  entries: {
                    populated: { label: 'Populated', description: 'Companies imported.', order: 10 },
                    qualified: { label: 'Qualified', description: 'Companies qualified.', order: 20 }
                  }
                },
                'sales.lead-gen:catalog/contact-stage': {
                  id: 'sales.lead-gen:catalog/contact-stage',
                  label: 'Contact Stages',
                  ownerSystemId: 'sales.lead-gen',
                  kind: 'stage',
                  appliesTo: 'sales.lead-gen:object/contact',
                  entries: {
                    discovered: { label: 'Discovered', description: 'Contacts found.', order: 10 },
                    verified: { label: 'Verified', description: 'Contacts verified.', order: 20 }
                  }
                }
              }
            }
          }
        }
      }
    },
    resources: {
      'lead-gen-api-workflow': {
        id: 'lead-gen-api-workflow',
        kind: 'workflow',
        systemPath: 'sales.lead-gen',
        status: 'active',
        order: 10,
        ontology: {
          actions: ['sales.lead-gen:action/api'],
          primaryAction: 'sales.lead-gen:action/api',
          reads: [
            'sales.lead-gen:object/list',
            'sales.lead-gen:object/company',
            'sales.lead-gen:object/contact'
          ],
          usesCatalogs: [
            'sales.lead-gen:catalog/build-template',
            'sales.lead-gen:catalog/template-step.local-services',
            'sales.lead-gen:catalog/company-stage',
            'sales.lead-gen:catalog/contact-stage'
          ]
        }
      }
    }
  }
}

function buildMinimalLeadGenAndCrmModel(options: { includeHandoff?: boolean } = {}): OrganizationModel {
  const crmCatalogEntries = Object.fromEntries(
    CRM_PIPELINE_DEFINITION.stages.map((stage, idx) => [
      stage.stageKey,
      {
        key: stage.stageKey,
        label: stage.label,
        order: (idx + 1) * 10,
        color: stage.color ?? 'gray',
        states: stage.states
      }
    ])
  )
  const model = buildMinimalLeadGenModel()
  const salesSystem = model.systems.sales
  if (salesSystem === undefined) throw new Error('Minimal lead-gen fixture is missing sales system')

  salesSystem.systems = {
    ...(salesSystem.systems ?? {}),
    crm: {
      id: 'sales.crm',
      label: 'CRM',
      order: 80,
      apiInterface: {
        lifecycle: 'active',
        readinessProfile: CRM_API_INTERFACE.readinessProfile,
        resourceIds: ['crm-api-workflow']
      },
      ontology: {
        objectTypes: {
          'sales.crm:object/deal': {
            id: 'sales.crm:object/deal',
            label: 'Deal',
            ownerSystemId: 'sales.crm'
          }
        },
        catalogTypes: {
          'sales.crm:catalog/crm.pipeline': {
            id: 'sales.crm:catalog/crm.pipeline',
            label: 'CRM',
            ownerSystemId: 'sales.crm',
            kind: 'pipeline',
            entries: crmCatalogEntries
          }
        }
      }
    }
  }

  model.resources = {
    ...model.resources,
    'crm-api-workflow': {
      id: 'crm-api-workflow',
      kind: 'workflow',
      systemPath: 'sales.crm',
      status: 'active',
      order: 20,
      ontology: {
        actions: ['sales.crm:action/api'],
        primaryAction: 'sales.crm:action/api',
        usesCatalogs: ['sales.crm:catalog/crm.pipeline']
      }
    }
  }

  if (options.includeHandoff === true) {
    const leadGenSystem = salesSystem.systems['lead-gen']
    if (leadGenSystem === undefined) throw new Error('Minimal fixture is missing lead-gen system')
    model.resources = {
      ...model.resources,
      'lead-gen-crm-handoff-workflow': {
        id: 'lead-gen-crm-handoff-workflow',
        kind: 'workflow',
        systemPath: 'sales.lead-gen',
        status: 'active',
        order: 30,
        ontology: {
          actions: ['sales.lead-gen:action/handoff'],
          primaryAction: 'sales.lead-gen:action/handoff',
          reads: [
            'sales.lead-gen:object/list',
            'sales.lead-gen:object/company',
            'sales.lead-gen:object/contact'
          ],
          usesCatalogs: [
            'sales.lead-gen:catalog/build-template',
            'sales.lead-gen:catalog/template-step.local-services',
            'sales.lead-gen:catalog/company-stage',
            'sales.lead-gen:catalog/contact-stage',
            'sales.crm:catalog/crm.pipeline'
          ]
        }
      }
    }
    model.topology = {
      version: 1,
      relationships: {
        'lead-gen-crm-handoff': {
          from: { kind: 'resource', id: 'lead-gen-crm-handoff-workflow' },
          kind: 'uses',
          to: { kind: 'resource', id: 'crm-api-workflow' },
          required: true,
          metadata: {
            systemInterfaceGrant: {
              consumer: {
                systemPath: LEAD_GEN_CRM_HANDOFF_INTERFACE.systemPath,
                interfaceKey: LEAD_GEN_CRM_HANDOFF_INTERFACE.interfaceKey
              },
              provider: {
                systemPath: CRM_API_INTERFACE.systemPath,
                interfaceKey: CRM_API_INTERFACE.interfaceKey
              },
              resourceIds: ['lead-gen-crm-handoff-workflow'],
              ontologyIds: ['sales.crm:catalog/crm.pipeline']
            }
          }
        }
      }
    }
  }

  return model
}

function buildMinimalCrmModel(): OrganizationModel {
  return buildMinimalLeadGenAndCrmModel()
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

const VALID_UUID = '00000000-0000-4000-8000-000000000001'
const ISO_TS = '2026-04-27T12:34:56.000Z'
const VALID_COMPANY_STAGE_KEY = 'qualified'
const VALID_CONTACT_STAGE_KEY = 'verified'
const PRIORITY = {
  bucketKey: 'waiting' as const,
  rank: 30,
  label: 'Waiting',
  color: 'blue',
  reason: 'No immediate response or follow-up is due.',
  latestActivityAt: ISO_TS,
  nextActionAt: null
}

// ---------------------------------------------------------------------------
// DealStageSchema
// ---------------------------------------------------------------------------

describe('DealStageSchema', () => {
  const crmStageKeys = CRM_PIPELINE_DEFINITION.stages.map((stage) => stage.stageKey)

  // DealStageSchema / CrmStageKeySchema / CrmStateKeySchema are now transport schemas
  // (z.string().trim().min(1)). Closed-catalog membership is model-owned and enforced
  // caller-side via model-injected validators. See api-schemas.ts comment.
  it('canonical CRM stages from @repo/elevasis-core are the expected 6-stage pipeline', () => {
    expect(crmStageKeys).toEqual(['interested', 'proposal', 'closing', 'closed_won', 'closed_lost', 'nurturing'])
  })

  it.each(crmStageKeys)('accepts canonical stage "%s"', (stage) => {
    expect(DealStageSchema.safeParse(stage).success).toBe(true)
  })

  // Transport contract: any non-empty string is accepted; closed-catalog enforcement is caller-side.
  it('accepts any non-empty stage string (closed catalog is model-owned), rejects empty', () => {
    expect(DealStageSchema.safeParse('open').success).toBe(true)
    expect(DealStageSchema.safeParse('custom_stage').success).toBe(true)
    expect(DealStageSchema.safeParse('').success).toBe(false)
  })

  // Transport contract: any non-empty state string is accepted; closed-catalog enforcement is caller-side.
  it('accepts any non-empty CRM state string (closed catalog is model-owned), rejects empty', () => {
    expect(CrmStateKeySchema.safeParse('custom_state').success).toBe(true)
    expect(CrmStateKeySchema.safeParse('discovery_replied').success).toBe(true)
    expect(CrmStateKeySchema.safeParse('').success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// Ontology validation contracts
// ---------------------------------------------------------------------------

describe('business ontology validation contracts', () => {
  it('derives compatibility interface constants from the canonical profile registry', () => {
    expect(SYSTEM_INTERFACE_PROFILES).toEqual([
      LEAD_GEN_API_INTERFACE,
      CRM_API_INTERFACE,
      LEAD_GEN_CRM_HANDOFF_INTERFACE
    ])
  })

  it('computes structured readiness for a ready lead-gen API interface without CRM', () => {
    const model = buildMinimalLeadGenModel()
    const readiness = computeInterfaceReadiness(model, LEAD_GEN_API_INTERFACE)

    expect(readiness.ready).toBe(true)
    expect(readiness.readinessProfile).toBe(LEAD_GEN_API_INTERFACE.readinessProfile)
    expect(readiness.scopedResourceIds).toEqual(['lead-gen-api-workflow'])
    expect(readiness.issues).toEqual([])
  })

  it('lead-gen API validation passes without CRM when list/company/contact readiness exists', () => {
    const model = buildMinimalLeadGenModel()
    const index = compileLeadGenApiOntologyValidationIndex(model)

    expect(index.ontology.catalogTypes[CRM_PIPELINE_CATALOG_ONTOLOGY_ID]).toBeUndefined()
    expect(index.ontology.catalogTypes[LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID]).toBeDefined()
    expect(index.leadGenStageCatalog).toBeDefined()
  })

  it('CRM API validation supplies pipeline readiness without requiring lead-gen contracts', () => {
    const model = buildMinimalCrmModel()
    const index = compileCrmApiOntologyValidationIndex(model)

    expect(index.crmPipelineCatalog).toBeDefined()
    expect(index.crmPipelineCatalog.entries).toHaveProperty('interested')
  })

  it('computes structured diagnostics when an interface marker is missing', () => {
    const model = buildMinimalLeadGenAndCrmModel()
    delete model.systems.sales?.systems?.['lead-gen']?.apiInterface

    const readiness = computeInterfaceReadiness(model, LEAD_GEN_API_INTERFACE)

    expect(readiness.ready).toBe(false)
    expect(readiness.issues).toMatchObject([{ family: 'SYSTEM_INTERFACE_MISSING', code: 'missing-interface' }])
  })

  it('computes structured diagnostics when an interface marker is inactive', () => {
    const model = buildMinimalLeadGenModel()
    const apiInterface = model.systems.sales?.systems?.['lead-gen']?.apiInterface
    if (apiInterface === undefined) throw new Error('Minimal fixture is missing lead-gen API interface')
    apiInterface.lifecycle = 'disabled'

    const readiness = computeInterfaceReadiness(model, LEAD_GEN_API_INTERFACE)

    expect(readiness.ready).toBe(false)
    expect(readiness.issues).toEqual(
      expect.arrayContaining([expect.objectContaining({ family: 'SYSTEM_INTERFACE_DISABLED', code: 'inactive-interface' })])
    )
  })

  it('computes structured diagnostics when scoped API resources are absent', () => {
    const model = buildMinimalLeadGenModel()
    const apiInterface = model.systems.sales?.systems?.['lead-gen']?.apiInterface
    if (apiInterface === undefined) throw new Error('Minimal fixture is missing lead-gen API interface')
    apiInterface.resourceIds = []

    const readiness = computeInterfaceReadiness(model, LEAD_GEN_API_INTERFACE)

    expect(readiness.ready).toBe(false)
    expect(readiness.issues).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ family: 'SYSTEM_INTERFACE_NOT_READY', code: 'missing-scoped-resources' })
      ])
    )
  })

  it('computes structured diagnostics when a scoped API resource id is missing', () => {
    const model = buildMinimalLeadGenModel()
    const apiInterface = model.systems.sales?.systems?.['lead-gen']?.apiInterface
    if (apiInterface === undefined) throw new Error('Minimal fixture is missing lead-gen API interface')
    apiInterface.resourceIds = ['missing-lead-gen-api-workflow']

    const readiness = computeInterfaceReadiness(model, LEAD_GEN_API_INTERFACE)

    expect(readiness.ready).toBe(false)
    expect(readiness.issues).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          family: 'SYSTEM_INTERFACE_NOT_READY',
          code: 'missing-resource',
          ref: 'missing-lead-gen-api-workflow',
          path: 'systems.sales.lead-gen.apiInterface.resourceIds.0'
        })
      ])
    )
  })

  it('computes structured diagnostics when a scoped API resource belongs to another System', () => {
    const model = buildMinimalLeadGenAndCrmModel()
    const apiInterface = model.systems.sales?.systems?.['lead-gen']?.apiInterface
    if (apiInterface === undefined) throw new Error('Minimal fixture is missing lead-gen API interface')
    apiInterface.resourceIds = ['crm-api-workflow']

    const readiness = computeInterfaceReadiness(model, LEAD_GEN_API_INTERFACE)

    expect(readiness.ready).toBe(false)
    expect(readiness.issues).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          family: 'SYSTEM_INTERFACE_INVALID',
          code: 'resource-system-mismatch',
          ref: 'crm-api-workflow',
          path: 'systems.sales.lead-gen.apiInterface.resourceIds.0'
        })
      ])
    )
  })

  it('computes structured diagnostics when required ontology exists but no scoped resource binds it', () => {
    const model = buildMinimalLeadGenModel()
    const resource = model.resources['lead-gen-api-workflow']
    if (resource === undefined) throw new Error('Minimal fixture is missing lead-gen API resource')
    resource.ontology = {
      ...resource.ontology,
      reads: ['sales.lead-gen:object/list', 'sales.lead-gen:object/company']
    }

    const readiness = computeInterfaceReadiness(model, LEAD_GEN_API_INTERFACE)

    expect(readiness.ready).toBe(false)
    expect(readiness.issues).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          family: 'SYSTEM_INTERFACE_NOT_READY',
          code: 'missing-resource-binding',
          ref: 'sales.lead-gen:object/contact'
        })
      ])
    )
  })

  it('computes structured diagnostics when required content is empty', () => {
    const model = buildMinimalLeadGenModel()
    const catalog = model.systems.sales?.systems?.['lead-gen']?.ontology?.catalogTypes?.[
      'sales.lead-gen:catalog/build-template'
    ]
    if (catalog === undefined) throw new Error('Minimal fixture is missing build-template catalog')
    catalog.entries = {}

    const readiness = computeInterfaceReadiness(model, LEAD_GEN_API_INTERFACE)

    expect(readiness.ready).toBe(false)
    expect(readiness.issues).toEqual(
      expect.arrayContaining([expect.objectContaining({ family: 'SYSTEM_INTERFACE_NOT_READY', code: 'empty-catalog' })])
    )
  })

  it('computes structured diagnostics for an unknown readiness profile', () => {
    const model = buildMinimalLeadGenModel()
    const apiInterface = model.systems.sales?.systems?.['lead-gen']?.apiInterface
    if (apiInterface === undefined) throw new Error('Minimal fixture is missing lead-gen API interface')
    ;(apiInterface as { readinessProfile?: string }).readinessProfile = 'sales.lead-gen.unknown'

    const readiness = computeInterfaceReadiness(model, LEAD_GEN_API_INTERFACE)

    expect(readiness.ready).toBe(false)
    expect(readiness.issues).toMatchObject([{ family: 'SYSTEM_INTERFACE_INVALID', code: 'unknown-readiness-profile' }])
    expect(readiness.issues[0]?.message).toContain('Supported profiles: "sales.lead-gen.api", "sales.crm.api", "sales.lead-gen.crm-handoff"')
    expect(readiness.issues[0]?.message).toContain('Custom Systems should not declare apiInterface')
    expect(readiness.issues[0]?.message).toContain('workflows/operations plus ontology, resources, and topology')
  })

  it('lead-gen-to-CRM handoff validation fails when the scoped topology grant is absent', () => {
    const model = buildMinimalLeadGenAndCrmModel({ includeHandoff: true })
    model.topology.relationships = {}

    expect(() => compileLeadGenCrmHandoffOntologyValidationIndex(model)).toThrow('missing-topology-grant')
  })

  it('lead-gen-to-CRM handoff readiness reports provider interface failures as bridge diagnostics', () => {
    const model = buildMinimalLeadGenAndCrmModel({ includeHandoff: true })
    const crmInterface = model.systems.sales?.systems?.crm?.apiInterface
    if (crmInterface === undefined) throw new Error('Minimal fixture is missing CRM API interface')
    crmInterface.lifecycle = 'disabled'

    const readiness = computeInterfaceReadiness(model, LEAD_GEN_CRM_HANDOFF_INTERFACE)

    expect(readiness.ready).toBe(false)
    expect(readiness.issues).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          family: 'SYSTEM_BRIDGE_NOT_READY',
          code: 'inactive-interface',
          path: 'systems.sales.crm.apiInterface.lifecycle'
        })
      ])
    )
  })

  it('lead-gen-to-CRM handoff validation requires the scoped topology grant and CRM pipeline readiness', () => {
    const model = buildMinimalLeadGenAndCrmModel({ includeHandoff: true })
    const index = compileLeadGenCrmHandoffOntologyValidationIndex(model)

    expect(index.bridgeGrant).toBeDefined()
    expect(index.leadGenStageCatalog).toBeDefined()
    expect(index.crmPipelineCatalog).toBeDefined()
  })

  it('lead-gen-to-CRM handoff validation fails when CRM pipeline readiness is absent', () => {
    const model = buildMinimalLeadGenAndCrmModel({ includeHandoff: true })
    const crmSystem = model.systems.sales?.systems?.crm
    if (crmSystem?.ontology?.catalogTypes === undefined) throw new Error('Minimal fixture is missing CRM catalog types')
    delete crmSystem.ontology.catalogTypes['sales.crm:catalog/crm.pipeline']

    expect(() => compileLeadGenCrmHandoffOntologyValidationIndex(model)).toThrow(CRM_PIPELINE_CATALOG_ONTOLOGY_ID)
  })

  // compileBusinessOntologyValidationIndex now requires a model arg — no default singleton.
  // The canonical Elevasis CRM pipeline catalog is model-owned (authored in @repo/elevasis-core
  // canonicalOrganizationModel at 'sales.crm:catalog/crm.pipeline'). Tests here use a
  // minimal inline model derived from CRM_PIPELINE_DEFINITION so packages/core stays
  // independent of @repo/elevasis-core.
  it('compiles CRM and lead-gen catalog bridge records from a model-injected CRM catalog', () => {
    const model = buildMinimalCrmModel()
    const index = compileBusinessOntologyValidationIndex(model)
    expect(index.ontology.catalogTypes[CRM_PIPELINE_CATALOG_ONTOLOGY_ID]).toBeDefined()
    expect(index.ontology.catalogTypes[LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID]).toBeDefined()
  })

  it('model-injected index supplies crmPipelineCatalog and leadGenStageCatalog', () => {
    const model = buildMinimalCrmModel()
    const index = compileBusinessOntologyValidationIndex(model)
    expect(index.crmPipelineCatalog).toBeDefined()
    expect(index.leadGenStageCatalog).toBeDefined()
  })

  it('does not publish Elevasis lead-gen action ids through the generic core ontology bridge', () => {
    // Leak guard: a model that contains no Elevasis-specific lead-gen action types must
    // produce an index whose actionTypesByLegacyId does not include the old singleton
    // legacy IDs ('send_reply', 'lead-gen.company.source').  These IDs were only present
    // in the removed module-level singleton that pre-compiled against a hard-coded tenant
    // model.  If this test fails, the de-leak has been reverted.
    const model = buildMinimalCrmModel()
    const index = compileBusinessOntologyValidationIndex(model)
    expect(index.actionTypesByLegacyId['send_reply']).toBeUndefined()
    expect(index.actionTypesByLegacyId['lead-gen.company.source']).toBeUndefined()
  })
})

// ---------------------------------------------------------------------------
// TransitionItemRequestSchema
// ---------------------------------------------------------------------------

describe('TransitionItemRequestSchema', () => {
  const valid = {
    pipelineKey: 'lead-gen',
    stageKey: 'interested',
    stateKey: null
  }

  it('accepts a minimal valid payload', () => {
    expect(TransitionItemRequestSchema.safeParse(valid).success).toBe(true)
  })

  it('accepts stateKey as null', () => {
    const result = TransitionItemRequestSchema.safeParse({ ...valid, stateKey: null })
    expect(result.success).toBe(true)
  })

  it('accepts stateKey as a string', () => {
    const result = TransitionItemRequestSchema.safeParse({ ...valid, stateKey: 'discovery_replied' })
    expect(result.success).toBe(true)
  })

  it('accepts stateKey as undefined (optional)', () => {
    const { stateKey: _omit, ...withoutState } = valid
    const result = TransitionItemRequestSchema.safeParse(withoutState)
    expect(result.success).toBe(true)
  })

  it('accepts a valid ISO datetime for expectedUpdatedAt', () => {
    const result = TransitionItemRequestSchema.safeParse({ ...valid, expectedUpdatedAt: ISO_TS })
    expect(result.success).toBe(true)
  })

  it('rejects a non-ISO datetime for expectedUpdatedAt', () => {
    const result = TransitionItemRequestSchema.safeParse({ ...valid, expectedUpdatedAt: 'not-a-date' })
    expect(result.success).toBe(false)
  })

  it('accepts all canonical CRM deal stages', () => {
    for (const stageKey of CRM_PIPELINE_DEFINITION.stages.map((stage) => stage.stageKey)) {
      expect(TransitionItemRequestSchema.safeParse({ pipelineKey: 'crm', stageKey, stateKey: null }).success).toBe(true)
    }
  })

  it('accepts catalog-derived CRM state keys', () => {
    for (const stateKey of CRM_PIPELINE_DEFINITION.stages.flatMap((stage) =>
      stage.states.map((state) => state.stateKey)
    )) {
      expect(TransitionItemRequestSchema.safeParse({ ...valid, stateKey }).success).toBe(true)
    }
  })

  it('accepts lead-gen pipeline stage/state pairs from LEAD_GEN_PIPELINE_DEFINITIONS', () => {
    for (const pipelineDefinitions of Object.values(LEAD_GEN_PIPELINE_DEFINITIONS)) {
      for (const pipeline of pipelineDefinitions) {
        for (const stage of pipeline.stages) {
          for (const state of stage.states) {
            expect(
              TransitionItemRequestSchema.safeParse({
                pipelineKey: pipeline.pipelineKey,
                stageKey: stage.stageKey,
                stateKey: state.stateKey
              }).success
            ).toBe(true)
          }
        }
      }
    }
  })

  it('rejects an empty pipelineKey', () => {
    const result = TransitionItemRequestSchema.safeParse({ ...valid, pipelineKey: '' })
    expect(result.success).toBe(false)
  })

  it('rejects an empty stageKey', () => {
    const result = TransitionItemRequestSchema.safeParse({ ...valid, stageKey: '' })
    expect(result.success).toBe(false)
  })

  it('accepts unknown non-empty stage and state keys for generic substrate transitions', () => {
    expect(TransitionItemRequestSchema.safeParse({ ...valid, stageKey: 'custom_stage' }).success).toBe(true)
    expect(TransitionItemRequestSchema.safeParse({ ...valid, stateKey: 'custom_state' }).success).toBe(true)
  })

  it('rejects unknown top-level fields (strict mode)', () => {
    const result = TransitionItemRequestSchema.safeParse({ ...valid, unknownField: 'x' })
    expect(result.success).toBe(false)
  })

  it('rejects missing pipelineKey', () => {
    const { pipelineKey: _omit, ...missing } = valid
    const result = TransitionItemRequestSchema.safeParse(missing)
    expect(result.success).toBe(false)
  })

  it('rejects missing stageKey', () => {
    const { stageKey: _omit, ...missing } = valid
    const result = TransitionItemRequestSchema.safeParse(missing)
    expect(result.success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// CrmTransitionItemRequestSchema
// ---------------------------------------------------------------------------

describe('CrmTransitionItemRequestSchema', () => {
  const valid = {
    pipelineKey: 'crm',
    stageKey: 'interested',
    stateKey: null
  }

  it('accepts catalog-derived CRM stage and state keys', () => {
    for (const stageKey of CRM_PIPELINE_DEFINITION.stages.map((stage) => stage.stageKey)) {
      expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stageKey }).success).toBe(true)
    }

    for (const stateKey of CRM_PIPELINE_DEFINITION.stages.flatMap((stage) =>
      stage.states.map((state) => state.stateKey)
    )) {
      expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stateKey }).success).toBe(true)
    }
  })

  // Transport contract: CrmStageKeySchema/CrmStateKeySchema are now z.string().trim().min(1).
  // Unknown non-empty stage/state keys are accepted at the schema layer; closed-catalog
  // membership is enforced caller-side via model-injected validators.
  it('accepts generic non-empty pipeline keys and any non-empty CRM stage/state keys (transport contract)', () => {
    expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, pipelineKey: 'lead-gen' }).success).toBe(true)
    expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stageKey: 'unknown_stage' }).success).toBe(true)
    expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stateKey: 'unknown_state' }).success).toBe(true)
  })

  it('still rejects empty stage and state keys (transport minimum length)', () => {
    expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stageKey: '' }).success).toBe(false)
    expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stateKey: '' }).success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// TransitionDealStateRequestSchema
// ---------------------------------------------------------------------------

describe('TransitionDealStateRequestSchema', () => {
  it('accepts catalog-derived CRM state keys', () => {
    for (const stateKey of CRM_PIPELINE_DEFINITION.stages.flatMap((stage) =>
      stage.states.map((state) => state.stateKey)
    )) {
      expect(TransitionDealStateRequestSchema.safeParse({ stateKey }).success).toBe(true)
    }
  })

  // Transport contract: CrmStateKeySchema is now z.string().trim().min(1).
  // Unknown non-empty state keys are accepted at the schema layer; closed-catalog
  // enforcement is caller-side via model-injected validators.
  it('accepts any non-empty state string (transport contract), rejects empty', () => {
    expect(TransitionDealStateRequestSchema.safeParse({ stateKey: 'unknown_state' }).success).toBe(true)
    expect(TransitionDealStateRequestSchema.safeParse({ stateKey: '' }).success).toBe(false)
  })

  it('preserves strict request schema behavior', () => {
    expect(
      TransitionDealStateRequestSchema.safeParse({
        stateKey: CRM_PIPELINE_DEFINITION.stages[0]?.states[0]?.stateKey,
        extra: 'x'
      }).success
    ).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// ExecuteActionRequestSchema
// ---------------------------------------------------------------------------

describe('ExecuteActionRequestSchema', () => {
  it('accepts an empty object (payload is optional)', () => {
    expect(ExecuteActionRequestSchema.safeParse({}).success).toBe(true)
  })

  it('accepts a payload record with arbitrary string keys', () => {
    const result = ExecuteActionRequestSchema.safeParse({
      payload: { channel: 'email', count: 5, nested: { ok: true } }
    })
    expect(result.success).toBe(true)
  })

  it('accepts an empty payload record', () => {
    expect(ExecuteActionRequestSchema.safeParse({ payload: {} }).success).toBe(true)
  })

  it('rejects unknown top-level fields (strict mode)', () => {
    const result = ExecuteActionRequestSchema.safeParse({ payload: {}, extra: 'bad' })
    expect(result.success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// CreateDealNoteRequestSchema
// ---------------------------------------------------------------------------

describe('CreateDealNoteRequestSchema', () => {
  it('accepts a valid body', () => {
    expect(CreateDealNoteRequestSchema.safeParse({ body: 'Hello' }).success).toBe(true)
  })

  it('accepts a single character after trim', () => {
    expect(CreateDealNoteRequestSchema.safeParse({ body: 'a' }).success).toBe(true)
  })

  it('trims whitespace and accepts content that remains non-empty', () => {
    const result = CreateDealNoteRequestSchema.safeParse({ body: '  note  ' })
    expect(result.success).toBe(true)
    if (result.success) expect(result.data.body).toBe('note')
  })

  it('rejects a whitespace-only string (empty after trim)', () => {
    expect(CreateDealNoteRequestSchema.safeParse({ body: '   ' }).success).toBe(false)
  })

  it('rejects an empty string', () => {
    expect(CreateDealNoteRequestSchema.safeParse({ body: '' }).success).toBe(false)
  })

  it('accepts a body at the max length boundary (10000 chars)', () => {
    expect(CreateDealNoteRequestSchema.safeParse({ body: 'a'.repeat(10000) }).success).toBe(true)
  })

  it('rejects a body exceeding the max length (10001 chars)', () => {
    expect(CreateDealNoteRequestSchema.safeParse({ body: 'a'.repeat(10001) }).success).toBe(false)
  })

  it('rejects unknown top-level fields (strict mode)', () => {
    expect(CreateDealNoteRequestSchema.safeParse({ body: 'note', extra: 'x' }).success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// CreateDealTaskRequestSchema
// ---------------------------------------------------------------------------

describe('CreateDealTaskRequestSchema', () => {
  const valid = { title: 'Follow up call' }

  it('accepts a minimal valid payload (title only)', () => {
    expect(CreateDealTaskRequestSchema.safeParse(valid).success).toBe(true)
  })

  it('kind is optional and accepts all valid values', () => {
    for (const kind of ['call', 'email', 'meeting', 'other'] as const) {
      expect(CreateDealTaskRequestSchema.safeParse({ ...valid, kind }).success).toBe(true)
    }
  })

  it('rejects an invalid kind value', () => {
    expect(CreateDealTaskRequestSchema.safeParse({ ...valid, kind: 'sms' }).success).toBe(false)
  })

  it('accepts a valid ISO datetime for dueAt', () => {
    expect(CreateDealTaskRequestSchema.safeParse({ ...valid, dueAt: ISO_TS }).success).toBe(true)
  })

  it('rejects an invalid datetime for dueAt', () => {
    expect(CreateDealTaskRequestSchema.safeParse({ ...valid, dueAt: 'tomorrow' }).success).toBe(false)
  })

  it('accepts null for dueAt', () => {
    expect(CreateDealTaskRequestSchema.safeParse({ ...valid, dueAt: null }).success).toBe(true)
  })

  it('accepts a valid UUID for assigneeUserId', () => {
    expect(CreateDealTaskRequestSchema.safeParse({ ...valid, assigneeUserId: VALID_UUID }).success).toBe(true)
  })

  it('rejects a non-UUID for assigneeUserId', () => {
    expect(CreateDealTaskRequestSchema.safeParse({ ...valid, assigneeUserId: 'not-a-uuid' }).success).toBe(false)
  })

  it('accepts null for assigneeUserId', () => {
    expect(CreateDealTaskRequestSchema.safeParse({ ...valid, assigneeUserId: null }).success).toBe(true)
  })

  it('rejects an empty title', () => {
    expect(CreateDealTaskRequestSchema.safeParse({ title: '' }).success).toBe(false)
  })

  it('rejects a title exceeding 255 chars', () => {
    expect(CreateDealTaskRequestSchema.safeParse({ title: 'a'.repeat(256) }).success).toBe(false)
  })

  it('rejects unknown top-level fields (strict mode)', () => {
    expect(CreateDealTaskRequestSchema.safeParse({ ...valid, bogus: true }).success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// ListDealsQuerySchema
// ---------------------------------------------------------------------------

describe('ListDealsQuerySchema', () => {
  it('accepts an empty query (all defaults applied)', () => {
    const result = ListDealsQuerySchema.safeParse({})
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.limit).toBe(50)
      expect(result.data.offset).toBe(0)
    }
  })

  it('coerces limit from string "50" to number 50', () => {
    const result = ListDealsQuerySchema.safeParse({ limit: '50' })
    expect(result.success).toBe(true)
    if (result.success) expect(result.data.limit).toBe(50)
  })

  it('coerces offset from string "20" to number 20', () => {
    const result = ListDealsQuerySchema.safeParse({ offset: '20' })
    expect(result.success).toBe(true)
    if (result.success) expect(result.data.offset).toBe(20)
  })

  it('rejects non-numeric string for limit', () => {
    expect(ListDealsQuerySchema.safeParse({ limit: 'abc' }).success).toBe(false)
  })

  it('rejects non-numeric string for offset', () => {
    expect(ListDealsQuerySchema.safeParse({ offset: 'abc' }).success).toBe(false)
  })

  it('rejects zero or negative limit (must be positive)', () => {
    expect(ListDealsQuerySchema.safeParse({ limit: '0' }).success).toBe(false)
    expect(ListDealsQuerySchema.safeParse({ limit: '-1' }).success).toBe(false)
  })

  it('rejects a negative offset', () => {
    expect(ListDealsQuerySchema.safeParse({ offset: '-1' }).success).toBe(false)
  })

  it('accepts zero offset', () => {
    expect(ListDealsQuerySchema.safeParse({ offset: '0' }).success).toBe(true)
  })

  it('accepts a valid stage filter with search and pagination', () => {
    const result = ListDealsQuerySchema.safeParse({
      stage: 'interested',
      search: 'acme',
      limit: '25',
      offset: '10'
    })
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.stage).toBe('interested')
      expect(result.data.search).toBe('acme')
      expect(result.data.limit).toBe(25)
      expect(result.data.offset).toBe(10)
    }
  })

  // Transport contract: DealStageSchema is now z.string().trim().min(1).
  // Any non-empty stage string is accepted; closed-catalog enforcement is caller-side.
  it('accepts any non-empty stage string (transport contract), rejects empty', () => {
    expect(ListDealsQuerySchema.safeParse({ stage: 'pipeline' }).success).toBe(true)
    expect(ListDealsQuerySchema.safeParse({ stage: '' }).success).toBe(false)
  })

  it('rejects unknown query fields (strict mode)', () => {
    expect(ListDealsQuerySchema.safeParse({ unknownParam: 'x' }).success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// UpdateContactRequestSchema
// ---------------------------------------------------------------------------

describe('UpdateContactRequestSchema', () => {
  it('rejects an object with no fields provided', () => {
    const result = UpdateContactRequestSchema.safeParse({})
    expect(result.success).toBe(false)
  })

  it('accepts a single field: firstName', () => {
    expect(UpdateContactRequestSchema.safeParse({ firstName: 'Alice' }).success).toBe(true)
  })

  it('accepts a single field: emailValid', () => {
    for (const v of ['VALID', 'INVALID', 'RISKY', 'UNKNOWN'] as const) {
      expect(UpdateContactRequestSchema.safeParse({ emailValid: v }).success).toBe(true)
    }
  })

  it('rejects an invalid emailValid value', () => {
    expect(UpdateContactRequestSchema.safeParse({ emailValid: 'maybe' }).success).toBe(false)
  })

  it('accepts a valid UUID for companyId', () => {
    expect(UpdateContactRequestSchema.safeParse({ companyId: VALID_UUID }).success).toBe(true)
  })

  it('rejects a non-UUID for companyId', () => {
    expect(UpdateContactRequestSchema.safeParse({ companyId: 'bad-id' }).success).toBe(false)
  })

  it('accepts a valid LinkedIn URL', () => {
    expect(UpdateContactRequestSchema.safeParse({ linkedinUrl: 'https://linkedin.com/in/alice' }).success).toBe(true)
  })

  it('rejects a non-URL for linkedinUrl', () => {
    expect(UpdateContactRequestSchema.safeParse({ linkedinUrl: 'not-a-url' }).success).toBe(false)
  })

  it('accepts multiple fields at once', () => {
    expect(UpdateContactRequestSchema.safeParse({ firstName: 'Alice', lastName: 'Smith', title: 'CEO' }).success).toBe(
      true
    )
  })

  it('accepts processingState keyed by the stage catalog', () => {
    const result = UpdateContactRequestSchema.safeParse({
      processingState: {
        [VALID_CONTACT_STAGE_KEY]: {
          status: 'no_result'
        }
      }
    })

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

  it('accepts deprecated pipelineStatus as a compatibility no-op', () => {
    expect(UpdateContactRequestSchema.safeParse({ pipelineStatus: 'emailed' }).success).toBe(true)
  })

  it('rejects unknown top-level fields (strict mode)', () => {
    expect(UpdateContactRequestSchema.safeParse({ firstName: 'Alice', unknown: 'x' }).success).toBe(false)
  })

  it('rejects an empty string for firstName (min 1 after trim)', () => {
    expect(UpdateContactRequestSchema.safeParse({ firstName: '' }).success).toBe(false)
  })

  it('rejects a firstName exceeding 255 chars', () => {
    expect(UpdateContactRequestSchema.safeParse({ firstName: 'a'.repeat(256) }).success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// CRM priority override contract
// ---------------------------------------------------------------------------

describe('CrmPriorityOverrideSchema', () => {
  it('accepts a valid partial organization override', () => {
    const result = CrmPriorityOverrideSchema.safeParse({
      enabled: true,
      staleAfterDays: 10,
      bucketOrder: ['needs_response', 'follow_up_due', 'stale', 'waiting', 'closed_low'],
      buckets: {
        needs_response: { label: 'Reply Now', color: 'red', rank: 5 },
        waiting: { label: 'Pending' }
      },
      followUpAfterDaysByStateKey: {
        discovery_link_sent: 2,
        custom_state: 4
      },
      closedStageKeys: ['closed_won', 'closed_lost']
    })

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

  it('rejects needsResponseStateKeys and needsResponseActivityTypes (removed fields)', () => {
    expect(CrmPriorityOverrideSchema.safeParse({ needsResponseStateKeys: ['x'] }).success).toBe(false)
    expect(CrmPriorityOverrideSchema.safeParse({ needsResponseActivityTypes: ['x'] }).success).toBe(false)
  })

  it('accepts sparse partial overrides', () => {
    const result = CrmPriorityOverrideSchema.safeParse({
      staleAfterDays: 21,
      buckets: {
        stale: { color: 'yellow' }
      }
    })

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

  it('rejects invalid unknown input shapes', () => {
    expect(CrmPriorityOverrideSchema.safeParse('bad').success).toBe(false)
    expect(CrmPriorityOverrideSchema.safeParse({ staleAfterDays: -1 }).success).toBe(false)
    expect(CrmPriorityOverrideSchema.safeParse({ buckets: { invalid_bucket: { label: 'Bad' } } }).success).toBe(false)
  })
})

describe('resolveCrmPriorityRuleConfig', () => {
  it('merges valid organization config overrides with generic core rules', () => {
    const resolved = resolveCrmPriorityRuleConfig({
      crm: {
        priority: {
          staleAfterDays: 7,
          bucketOrder: ['stale', 'needs_response'],
          buckets: {
            stale: { label: 'Dormant', color: 'yellow' },
            needs_response: { rank: 3 }
          },
          followUpAfterDaysByStateKey: { discovery_link_sent: 1 },
          closedStageKeys: ['won', 'lost']
        }
      }
    })

    expect(resolved.enabled).toBe(true)
    expect(resolved.staleAfterDays).toBe(7)
    expect(resolved.closedStageKeys).toEqual(['won', 'lost'])
    expect(resolved.followUpAfterDaysByStateKey.discovery_link_sent).toBe(1)
    expect(resolved.followUpAfterDaysByStateKey.reply_sent).toBeUndefined()

    const staleBucket = resolved.buckets.find((bucket) => bucket.bucketKey === 'stale')
    expect(staleBucket).toMatchObject({ label: 'Dormant', color: 'yellow', rank: 10 })

    const needsResponseBucket = resolved.buckets.find((bucket) => bucket.bucketKey === 'needs_response')
    expect(needsResponseBucket?.rank).toBe(3)
  })

  it('falls back to generic defaults for missing or invalid input', () => {
    expect(resolveCrmPriorityRuleConfig(undefined)).toMatchObject({
      enabled: true,
      staleAfterDays: DEFAULT_CRM_PRIORITY_RULE_CONFIG.staleAfterDays,
      closedStageKeys: []
    })

    expect(resolveCrmPriorityRuleConfig({ staleAfterDays: 0 })).toMatchObject({
      enabled: true,
      staleAfterDays: DEFAULT_CRM_PRIORITY_RULE_CONFIG.staleAfterDays,
      closedStageKeys: []
    })
  })
})

// ---------------------------------------------------------------------------
// Response schemas — smoke-level forward-compat checks
// ---------------------------------------------------------------------------

describe('evaluateCrmDealPriority', () => {
  const now = '2026-04-30T12:00:00.000Z'
  const baseInput = {
    stage_key: 'interested',
    state_key: null,
    activity_log: [],
    updated_at: '2026-04-29T12:00:00.000Z',
    created_at: '2026-04-01T12:00:00.000Z'
  }

  it('returns needs_response when the lead replied last (ownership us)', () => {
    // A reply_received inbound event makes ownership === 'us' → needs_response
    const priority = evaluateCrmDealPriority(
      {
        ...baseInput,
        state_key: 'discovery_replied',
        activity_log: [{ type: 'reply_received', timestamp: '2026-04-29T10:00:00.000Z' }]
      },
      { now, config: DEFAULT_CRM_PRIORITY_RULE_CONFIG }
    )
    expect(priority.bucketKey).toBe('needs_response')
    expect(priority.rank).toBe(10)
    expect(priority.reason).toBe('Lead replied last — we owe the next move.')
  })

  it('returns follow_up_due when configured follow-up window has elapsed', () => {
    const priority = evaluateCrmDealPriority(
      {
        ...baseInput,
        state_key: 'discovery_link_sent',
        activity_log: [{ type: 'action_taken', timestamp: '2026-04-25T12:00:00.000Z', actionKey: 'send_link' }],
        updated_at: '2026-04-25T12:00:00.000Z'
      },
      { now, config: DEFAULT_CRM_PRIORITY_RULE_CONFIG }
    )

    expect(priority.bucketKey).toBe('follow_up_due')
    expect(priority.nextActionAt).toBe('2026-04-28T12:00:00.000Z')
  })

  it('returns waiting when the next action is still in the future', () => {
    const priority = evaluateCrmDealPriority(
      {
        ...baseInput,
        state_key: 'discovery_link_sent',
        activity_log: [{ type: 'action_taken', timestamp: '2026-04-29T12:00:00.000Z', actionKey: 'send_link' }],
        updated_at: '2026-04-29T12:00:00.000Z'
      },
      { now, config: DEFAULT_CRM_PRIORITY_RULE_CONFIG }
    )

    expect(priority.bucketKey).toBe('waiting')
    expect(priority.nextActionAt).toBe('2026-05-02T12:00:00.000Z')
  })

  it('returns stale when there is no recent meaningful activity', () => {
    const priority = evaluateCrmDealPriority(
      { ...baseInput, updated_at: '2026-04-01T12:00:00.000Z', created_at: '2026-04-01T12:00:00.000Z' },
      { now, config: DEFAULT_CRM_PRIORITY_RULE_CONFIG }
    )

    expect(priority.bucketKey).toBe('stale')
  })

  it('returns closed_low for closed stages', () => {
    const priority = evaluateCrmDealPriority(
      { ...baseInput, stage_key: 'closed_lost' },
      { now, config: DEFAULT_CRM_PRIORITY_RULE_CONFIG }
    )
    expect(priority.bucketKey).toBe('closed_low')
    expect(priority.rank).toBe(50)
  })

  it('ignores malformed activity entries without throwing', () => {
    const priority = evaluateCrmDealPriority(
      {
        ...baseInput,
        activity_log: [null, 'bad', { type: 'reply_received', timestamp: 'not-a-date' }, { other: true }],
        updated_at: '2026-04-29T12:00:00.000Z'
      },
      { now }
    )

    expect(priority.bucketKey).toBe('waiting')
    expect(priority.latestActivityAt).toBe('2026-04-29T12:00:00.000Z')
  })

  it('returns a neutral waiting priority when CRM priority is disabled', () => {
    const config = resolveCrmPriorityRuleConfig({
      enabled: false,
      buckets: {
        waiting: { label: 'Priority Off', color: 'gray', rank: 999 }
      }
    })
    const priority = evaluateCrmDealPriority(
      {
        ...baseInput,
        state_key: 'discovery_replied',
        activity_log: [{ type: 'reply_received', timestamp: '2026-04-30T10:00:00.000Z' }]
      },
      { config, now }
    )

    expect(priority).toMatchObject({
      bucketKey: 'waiting',
      label: 'Priority Off',
      color: 'gray',
      rank: 999,
      reason: 'CRM priority evaluation is disabled.',
      nextActionAt: null
    })
  })
})

describe('DealListItemSchema', () => {
  it('accepts a deal with a derived priority object', () => {
    const result = DealListItemSchema.safeParse({
      id: VALID_UUID,
      organization_id: VALID_UUID,
      contact_id: null,
      contact_email: 'test@example.com',
      pipeline_key: 'crm',
      stage_key: 'interested',
      state_key: 'discovery_link_sent',
      activity_log: [],
      discovery_data: null,
      discovery_submitted_at: null,
      discovery_submitted_by: null,
      proposal_data: null,
      proposal_sent_at: null,
      proposal_pdf_url: null,
      signature_envelope_id: null,
      source_list_id: null,
      source_type: null,
      initial_fee: null,
      monthly_fee: null,
      closed_lost_at: null,
      closed_lost_reason: null,
      created_at: ISO_TS,
      updated_at: ISO_TS,
      priority: PRIORITY,
      ownership: null,
      nextAction: null,
      contact: null
    })

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

describe('DealDetailResponseSchema (forward-compat)', () => {
  const baseDeal = {
    id: VALID_UUID,
    organization_id: VALID_UUID,
    contact_id: null,
    contact_email: 'test@example.com',
    pipeline_key: 'crm',
    stage_key: null,
    state_key: null,
    activity_log: [],
    discovery_data: null,
    discovery_submitted_at: null,
    discovery_submitted_by: null,
    proposal_data: null,
    proposal_sent_at: null,
    proposal_pdf_url: null,
    signature_envelope_id: null,
    source_list_id: null,
    source_type: null,
    initial_fee: null,
    monthly_fee: null,
    closed_lost_at: null,
    closed_lost_reason: null,
    created_at: ISO_TS,
    updated_at: ISO_TS,
    priority: PRIORITY,
    ownership: null,
    nextAction: null,
    contact: null,
    conversation: {
      messages: []
    }
  }

  it('accepts a deal with null contact and an empty conversation', () => {
    expect(DealDetailResponseSchema.safeParse(baseDeal).success).toBe(true)
  })

  it('accepts conversation messages with preview-derived bodies', () => {
    const withConversation = {
      ...baseDeal,
      conversation: {
        messages: [
          {
            id: 'message-1',
            direction: 'inbound',
            fromEmail: 'lead@example.com',
            toEmail: 'sender@example.com',
            subject: 'Re: quick thought',
            body: 'Sure, send it over.',
            sentAt: ISO_TS
          }
        ]
      }
    }
    expect(DealDetailResponseSchema.safeParse(withConversation).success).toBe(true)
  })

  it('accepts a deal with nested contact that has null company', () => {
    const withContact = {
      ...baseDeal,
      contact: {
        id: VALID_UUID,
        first_name: 'Alice',
        last_name: 'Smith',
        email: 'alice@example.com',
        title: null,
        headline: null,
        linkedin_url: null,
        processing_state: null,
        enrichment_data: null,
        company: null
      }
    }
    expect(DealDetailResponseSchema.safeParse(withContact).success).toBe(true)
  })

  it('accepts thin client lineage refs on deal detail responses', () => {
    expect(
      DealDetailResponseSchema.safeParse({
        ...baseDeal,
        client_id: VALID_UUID,
        lineage: {
          list: null,
          projects: [],
          client: {
            id: VALID_UUID,
            name: 'Acme Client',
            status: 'active'
          }
        }
      }).success
    ).toBe(true)
  })

  it('accepts extra unknown fields at top level (not strict)', () => {
    expect(DealDetailResponseSchema.safeParse({ ...baseDeal, futureField: 'value' }).success).toBe(true)
  })
})

describe('DealListResponseSchema', () => {
  it('accepts a valid paginated list response', () => {
    const result = DealListResponseSchema.safeParse({
      data: [],
      total: 0,
      limit: 50,
      offset: 0
    })
    expect(result.success).toBe(true)
  })

  it('rejects non-integer total', () => {
    expect(DealListResponseSchema.safeParse({ data: [], total: 1.5, limit: 50, offset: 0 }).success).toBe(false)
  })
})

describe('DealNoteResponseSchema', () => {
  it('accepts a valid note response', () => {
    const result = DealNoteResponseSchema.safeParse({
      id: VALID_UUID,
      dealId: VALID_UUID,
      organizationId: VALID_UUID,
      authorUserId: null,
      body: 'A note',
      createdAt: ISO_TS,
      updatedAt: ISO_TS
    })
    expect(result.success).toBe(true)
  })

  it('accepts extra fields (not strict)', () => {
    const result = DealNoteResponseSchema.safeParse({
      id: VALID_UUID,
      dealId: VALID_UUID,
      organizationId: VALID_UUID,
      authorUserId: null,
      body: 'A note',
      createdAt: ISO_TS,
      updatedAt: ISO_TS,
      futureField: 'ignored'
    })
    expect(result.success).toBe(true)
  })
})

describe('DealTaskResponseSchema', () => {
  it('accepts a valid task response', () => {
    const result = DealTaskResponseSchema.safeParse({
      id: VALID_UUID,
      organizationId: VALID_UUID,
      dealId: VALID_UUID,
      title: 'Call prospect',
      description: null,
      kind: 'call',
      dueAt: null,
      assigneeUserId: null,
      completedAt: null,
      completedByUserId: null,
      createdAt: ISO_TS,
      updatedAt: ISO_TS,
      createdByUserId: null
    })
    expect(result.success).toBe(true)
  })

  it('rejects an invalid kind in task response', () => {
    const result = DealTaskResponseSchema.safeParse({
      id: VALID_UUID,
      organizationId: VALID_UUID,
      dealId: VALID_UUID,
      title: 'Call',
      description: null,
      kind: 'text_message',
      dueAt: null,
      assigneeUserId: null,
      completedAt: null,
      completedByUserId: null,
      createdAt: ISO_TS,
      updatedAt: ISO_TS,
      createdByUserId: null
    })
    expect(result.success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// ListStatusSchema
// ---------------------------------------------------------------------------

describe('ListStatusSchema', () => {
  it.each(['draft', 'enriching', 'launched', 'closing', 'archived'])('accepts "%s"', (status) => {
    expect(ListStatusSchema.safeParse(status).success).toBe(true)
  })

  it('rejects unknown status', () => {
    expect(ListStatusSchema.safeParse('active').success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// PipelineStageSchema
// ---------------------------------------------------------------------------

describe('PipelineStageSchema', () => {
  it('accepts non-empty model-owned stage keys', () => {
    expect(PipelineStageSchema.safeParse({ key: VALID_COMPANY_STAGE_KEY }).success).toBe(true)
    expect(PipelineStageSchema.safeParse({ key: 'tenant-custom-stage' }).success).toBe(true)
  })

  it('rejects an empty stage key', () => {
    const result = PipelineStageSchema.safeParse({ key: '' })
    expect(result.success).toBe(false)
  })

  it('accepts optional label, enabled, and order fields', () => {
    expect(PipelineStageSchema.safeParse({ key: 'scraped', label: 'Scraped', enabled: true, order: 1 }).success).toBe(
      true
    )
  })
})

// ---------------------------------------------------------------------------
// BuildPlanSnapshotSchema
// ---------------------------------------------------------------------------

describe('BuildPlanSnapshotSchema', () => {
  const validSnapshot = createBuildPlanSnapshotFromTemplate({
    id: 'tenant-template',
    label: 'Tenant Template',
    steps: [
      {
        id: 'source-companies',
        label: 'Companies found',
        primaryEntity: 'company',
        outputs: ['company'],
        stageKey: 'populated',
        dependencyMode: 'per-record-eligibility',
        actionKey: 'lead-gen.company.source',
        defaultBatchSize: 100,
        maxBatchSize: 250
      }
    ]
  })

  it('accepts a snapshot generated from a prospecting build template', () => {
    expect(BuildPlanSnapshotSchema.safeParse(validSnapshot).success).toBe(true)
  })

  it('accepts custom non-empty step stage keys at the transport boundary', () => {
    const result = BuildPlanSnapshotSchema.safeParse({
      ...validSnapshot,
      steps: [{ ...validSnapshot.steps[0], stageKey: 'made-up-stage' }]
    })

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

  it('accepts custom non-empty action keys at the transport boundary', () => {
    const result = BuildPlanSnapshotSchema.safeParse({
      ...validSnapshot,
      steps: [{ ...validSnapshot.steps[0], actionKey: 'lead-gen.missing.action' }]
    })

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

  it('rejects duplicate step ids', () => {
    const first = validSnapshot!.steps[0]!
    const second = validSnapshot!.steps[1]!
    const rest = validSnapshot!.steps.slice(2)
    const result = BuildPlanSnapshotSchema.safeParse({
      ...validSnapshot,
      steps: [first, { ...second, id: first.id }, ...rest]
    })

    expect(result.success).toBe(false)
  })

  it('rejects dependsOn references to unknown step ids', () => {
    const result = BuildPlanSnapshotSchema.safeParse({
      ...validSnapshot,
      steps: [{ ...validSnapshot!.steps[0], dependsOn: ['missing-step'] }]
    })

    expect(result.success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// ScrapingConfigSchema
// ---------------------------------------------------------------------------

describe('ScrapingConfigSchema', () => {
  it('accepts an empty object (all fields optional)', () => {
    expect(ScrapingConfigSchema.safeParse({}).success).toBe(true)
  })

  it('accepts a fully populated config', () => {
    expect(
      ScrapingConfigSchema.safeParse({
        vertical: 'SaaS',
        geography: 'USA',
        size: '10-50',
        apifyInput: { actorId: 'test' }
      }).success
    ).toBe(true)
  })

  it('rejects a vertical exceeding 255 chars', () => {
    expect(ScrapingConfigSchema.safeParse({ vertical: 'a'.repeat(256) }).success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// IcpRubricSchema
// ---------------------------------------------------------------------------

describe('IcpRubricSchema', () => {
  it('accepts an empty object', () => {
    expect(IcpRubricSchema.safeParse({}).success).toBe(true)
  })

  it('accepts valid minRating boundary values 0 and 5', () => {
    expect(IcpRubricSchema.safeParse({ minRating: 0 }).success).toBe(true)
    expect(IcpRubricSchema.safeParse({ minRating: 5 }).success).toBe(true)
  })

  it('rejects minRating above 5', () => {
    expect(IcpRubricSchema.safeParse({ minRating: 5.1 }).success).toBe(false)
  })

  it('rejects negative minRating', () => {
    expect(IcpRubricSchema.safeParse({ minRating: -1 }).success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// CreateListRequestSchema
// ---------------------------------------------------------------------------

describe('CreateListRequestSchema', () => {
  it('accepts a minimal valid payload (name only)', () => {
    expect(CreateListRequestSchema.safeParse({ name: 'My List' }).success).toBe(true)
  })

  it('rejects an empty name', () => {
    expect(CreateListRequestSchema.safeParse({ name: '' }).success).toBe(false)
  })

  it('rejects a name exceeding 255 chars', () => {
    expect(CreateListRequestSchema.safeParse({ name: 'a'.repeat(256) }).success).toBe(false)
  })

  it('accepts an optional status from ListStatusSchema', () => {
    expect(CreateListRequestSchema.safeParse({ name: 'X', status: 'draft' }).success).toBe(true)
  })

  it('accepts a known prospecting build template id', () => {
    const result = CreateListRequestSchema.safeParse({
      name: 'DTC Subscription Brands',
      buildTemplateId: 'dtc-subscription-apollo-clickup'
    })

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

  it('accepts tenant-owned build template ids for API-layer membership validation', () => {
    expect(CreateListRequestSchema.safeParse({ name: 'X', buildTemplateId: 'not-a-template' }).success).toBe(true)
  })

  it('rejects an invalid status', () => {
    expect(CreateListRequestSchema.safeParse({ name: 'X', status: 'active' }).success).toBe(false)
  })

  it('rejects unknown fields (strict mode)', () => {
    expect(CreateListRequestSchema.safeParse({ name: 'X', bogus: true }).success).toBe(false)
  })

  it('accepts nested scrapingConfig, icp, and pipelineConfig', () => {
    expect(
      CreateListRequestSchema.safeParse({
        name: 'SaaS List',
        scrapingConfig: { vertical: 'SaaS' },
        icp: { minReviewCount: 5 },
        pipelineConfig: { stages: [], dataMode: 'mock' }
      }).success
    ).toBe(true)
  })

  it('accepts create-time list-wide pipeline dataMode', () => {
    const result = CreateListRequestSchema.safeParse({
      name: 'DTC Demo',
      pipelineConfig: { dataMode: 'mock' }
    })

    expect(result.success).toBe(true)
    if (result.success) expect(result.data.pipelineConfig?.dataMode).toBe('mock')
  })
})

// ---------------------------------------------------------------------------
// UpdateListRequestSchema
// ---------------------------------------------------------------------------

describe('UpdateListRequestSchema', () => {
  it('rejects an object with none of the required fields', () => {
    const result = UpdateListRequestSchema.safeParse({})
    expect(result.success).toBe(false)
  })

  it('accepts providing only name', () => {
    expect(UpdateListRequestSchema.safeParse({ name: 'New Name' }).success).toBe(true)
  })

  it('accepts providing only description', () => {
    expect(UpdateListRequestSchema.safeParse({ description: 'Desc' }).success).toBe(true)
  })

  it('accepts providing only batchIds', () => {
    expect(UpdateListRequestSchema.safeParse({ batchIds: ['batch-1'] }).success).toBe(true)
  })

  it('accepts buildTemplateId when the change is explicitly confirmed', () => {
    expect(
      UpdateListRequestSchema.safeParse({
        buildTemplateId: 'dtc-subscription-apollo-clickup',
        confirmBuildTemplateChange: true
      }).success
    ).toBe(true)
  })

  it('rejects buildTemplateId without explicit confirmation', () => {
    expect(
      UpdateListRequestSchema.safeParse({
        buildTemplateId: 'dtc-subscription-apollo-clickup'
      }).success
    ).toBe(false)
  })

  it('rejects unknown fields (strict mode)', () => {
    expect(UpdateListRequestSchema.safeParse({ name: 'X', extra: 'bad' }).success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// UpdateListStatusRequestSchema
// ---------------------------------------------------------------------------

describe('UpdateListStatusRequestSchema', () => {
  it('accepts a valid status', () => {
    expect(UpdateListStatusRequestSchema.safeParse({ status: 'launched' }).success).toBe(true)
  })

  it('rejects an invalid status', () => {
    expect(UpdateListStatusRequestSchema.safeParse({ status: 'active' }).success).toBe(false)
  })

  it('rejects missing status field', () => {
    expect(UpdateListStatusRequestSchema.safeParse({}).success).toBe(false)
  })

  it('rejects unknown fields (strict mode)', () => {
    expect(UpdateListStatusRequestSchema.safeParse({ status: 'draft', extra: 'x' }).success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// UpdateListConfigRequestSchema
// ---------------------------------------------------------------------------

describe('UpdateListConfigRequestSchema', () => {
  it('rejects an object with none of the config fields', () => {
    expect(UpdateListConfigRequestSchema.safeParse({}).success).toBe(false)
  })

  it('accepts providing only scrapingConfig', () => {
    expect(UpdateListConfigRequestSchema.safeParse({ scrapingConfig: { vertical: 'SaaS' } }).success).toBe(true)
  })

  it('accepts providing only icp', () => {
    expect(UpdateListConfigRequestSchema.safeParse({ icp: { minReviewCount: 3 } }).success).toBe(true)
  })

  it('accepts providing only pipelineConfig', () => {
    expect(UpdateListConfigRequestSchema.safeParse({ pipelineConfig: { stages: [] } }).success).toBe(true)
  })

  it('accepts a data-mode-only pipelineConfig patch', () => {
    const result = UpdateListConfigRequestSchema.safeParse({ pipelineConfig: { dataMode: 'live' } })

    expect(result.success).toBe(true)
    if (result.success) expect(result.data.pipelineConfig?.dataMode).toBe('live')
  })

  it('rejects invalid pipelineConfig dataMode values', () => {
    expect(UpdateListConfigRequestSchema.safeParse({ pipelineConfig: { dataMode: 'demo' } }).success).toBe(false)
  })

  it('rejects unknown fields (strict mode)', () => {
    expect(UpdateListConfigRequestSchema.safeParse({ scrapingConfig: {}, bogus: true }).success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// PipelineConfigSchema
// ---------------------------------------------------------------------------

describe('PipelineConfigSchema', () => {
  it.each(['mock', 'live'])('accepts list-wide dataMode "%s"', (dataMode) => {
    expect(PipelineConfigSchema.safeParse({ dataMode }).success).toBe(true)
  })

  it('keeps dataMode optional for legacy stored configs', () => {
    expect(PipelineConfigSchema.safeParse({ stages: [{ key: 'populated' }] }).success).toBe(true)
  })
})

// ---------------------------------------------------------------------------
// AddCompaniesToListRequestSchema / AddContactsToListRequestSchema
// ---------------------------------------------------------------------------

describe('AddCompaniesToListRequestSchema', () => {
  it('accepts a valid list with one UUID', () => {
    expect(AddCompaniesToListRequestSchema.safeParse({ companyIds: [VALID_UUID] }).success).toBe(true)
  })

  it('rejects an empty array (min 1)', () => {
    expect(AddCompaniesToListRequestSchema.safeParse({ companyIds: [] }).success).toBe(false)
  })

  it('rejects more than 1000 IDs (max 1000)', () => {
    const ids = Array.from({ length: 1001 }, (_, i) => `00000000-0000-0000-0000-${String(i).padStart(12, '0')}`)
    expect(AddCompaniesToListRequestSchema.safeParse({ companyIds: ids }).success).toBe(false)
  })

  it('rejects non-UUID entries', () => {
    expect(AddCompaniesToListRequestSchema.safeParse({ companyIds: ['not-a-uuid'] }).success).toBe(false)
  })
})

describe('AddContactsToListRequestSchema', () => {
  it('accepts a valid list with one UUID', () => {
    expect(AddContactsToListRequestSchema.safeParse({ contactIds: [VALID_UUID] }).success).toBe(true)
  })

  it('rejects an empty array (min 1)', () => {
    expect(AddContactsToListRequestSchema.safeParse({ contactIds: [] }).success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// ListCompaniesQuerySchema
// ---------------------------------------------------------------------------

describe('ListCompaniesQuerySchema', () => {
  it('accepts an empty query (defaults applied)', () => {
    const result = ListCompaniesQuerySchema.safeParse({})
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.limit).toBe(50)
      expect(result.data.offset).toBe(0)
    }
  })

  it('coerces limit from string "100" to number 100', () => {
    const result = ListCompaniesQuerySchema.safeParse({ limit: '100' })
    expect(result.success).toBe(true)
    if (result.success) expect(result.data.limit).toBe(100)
  })

  it('rejects limit exceeding 5000', () => {
    expect(ListCompaniesQuerySchema.safeParse({ limit: '5001' }).success).toBe(false)
  })

  it('rejects limit less than 1', () => {
    expect(ListCompaniesQuerySchema.safeParse({ limit: '0' }).success).toBe(false)
  })

  it('accepts a valid status filter', () => {
    expect(ListCompaniesQuerySchema.safeParse({ status: 'active' }).success).toBe(true)
    expect(ListCompaniesQuerySchema.safeParse({ status: 'invalid' }).success).toBe(true)
  })

  it('rejects an invalid status', () => {
    expect(ListCompaniesQuerySchema.safeParse({ status: 'pending' }).success).toBe(false)
  })

  it('accepts includeAll as boolean-like strings', () => {
    const r1 = ListCompaniesQuerySchema.safeParse({ includeAll: 'true' })
    expect(r1.success).toBe(true)
    if (r1.success) expect(r1.data.includeAll).toBe(true)

    const r2 = ListCompaniesQuerySchema.safeParse({ includeAll: 'false' })
    expect(r2.success).toBe(true)
    if (r2.success) expect(r2.data.includeAll).toBe(false)
  })

  it('rejects unknown fields (strict mode)', () => {
    expect(ListCompaniesQuerySchema.safeParse({ unknownField: 'x' }).success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// ListContactsQuerySchema
// ---------------------------------------------------------------------------

describe('ListContactsQuerySchema', () => {
  it('accepts an empty query with defaults', () => {
    const result = ListContactsQuerySchema.safeParse({})
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.limit).toBe(5000)
      expect(result.data.offset).toBe(0)
    }
  })

  it('accepts openingLineIsNull as "true"', () => {
    const result = ListContactsQuerySchema.safeParse({ openingLineIsNull: 'true' })
    expect(result.success).toBe(true)
    if (result.success) expect(result.data.openingLineIsNull).toBe(true)
  })
})

// ---------------------------------------------------------------------------
// ListDealTasksDueQuerySchema
// ---------------------------------------------------------------------------

describe('ListDealTasksDueQuerySchema', () => {
  it('accepts an empty query', () => {
    expect(ListDealTasksDueQuerySchema.safeParse({}).success).toBe(true)
  })

  it.each(['overdue', 'today', 'today_and_overdue', 'upcoming'])('accepts window "%s"', (window) => {
    expect(ListDealTasksDueQuerySchema.safeParse({ window }).success).toBe(true)
  })

  it('rejects an invalid window value', () => {
    expect(ListDealTasksDueQuerySchema.safeParse({ window: 'this_week' }).success).toBe(false)
  })

  it('accepts a valid UUID for assigneeUserId', () => {
    expect(ListDealTasksDueQuerySchema.safeParse({ assigneeUserId: VALID_UUID }).success).toBe(true)
  })

  it('rejects unknown fields (strict mode)', () => {
    expect(ListDealTasksDueQuerySchema.safeParse({ window: 'today', extra: 'x' }).success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// CreateCompanyRequestSchema
// ---------------------------------------------------------------------------

describe('CreateCompanyRequestSchema', () => {
  it('accepts a minimal payload (name only)', () => {
    expect(CreateCompanyRequestSchema.safeParse({ name: 'Acme' }).success).toBe(true)
  })

  it('rejects an empty name', () => {
    expect(CreateCompanyRequestSchema.safeParse({ name: '' }).success).toBe(false)
  })

  it('rejects a non-URL for linkedinUrl', () => {
    expect(CreateCompanyRequestSchema.safeParse({ name: 'Acme', linkedinUrl: 'linkedin.com/co/acme' }).success).toBe(
      false
    )
  })

  it('accepts a valid URL for linkedinUrl', () => {
    expect(
      CreateCompanyRequestSchema.safeParse({ name: 'Acme', linkedinUrl: 'https://linkedin.com/company/acme' }).success
    ).toBe(true)
  })

  it('rejects unknown fields (strict mode)', () => {
    expect(CreateCompanyRequestSchema.safeParse({ name: 'Acme', bogus: true }).success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// UpdateCompanyRequestSchema
// ---------------------------------------------------------------------------

describe('UpdateCompanyRequestSchema', () => {
  it('rejects an object with no fields (at-least-one refine)', () => {
    expect(UpdateCompanyRequestSchema.safeParse({}).success).toBe(false)
  })

  it('accepts providing only name', () => {
    expect(UpdateCompanyRequestSchema.safeParse({ name: 'Acme Corp' }).success).toBe(true)
  })

  it('accepts providing only status', () => {
    expect(UpdateCompanyRequestSchema.safeParse({ status: 'active' }).success).toBe(true)
    expect(UpdateCompanyRequestSchema.safeParse({ status: 'invalid' }).success).toBe(true)
  })

  it('accepts processingState keyed by the stage catalog', () => {
    const result = UpdateCompanyRequestSchema.safeParse({
      processingState: {
        [VALID_COMPANY_STAGE_KEY]: {
          status: 'success',
          data: { score: 92 }
        }
      }
    })

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

  it('accepts deprecated pipelineStatus as a compatibility no-op', () => {
    expect(UpdateCompanyRequestSchema.safeParse({ pipelineStatus: 'emailed' }).success).toBe(true)
  })

  it('rejects empty processingState keys', () => {
    expect(
      UpdateCompanyRequestSchema.safeParse({
        processingState: {
          '': { status: 'success' }
        }
      }).success
    ).toBe(false)
  })

  it('accepts numEmployees of 0', () => {
    expect(UpdateCompanyRequestSchema.safeParse({ numEmployees: 0 }).success).toBe(true)
  })

  it('rejects negative numEmployees', () => {
    expect(UpdateCompanyRequestSchema.safeParse({ numEmployees: -1 }).success).toBe(false)
  })

  it('rejects unknown fields (strict mode)', () => {
    expect(UpdateCompanyRequestSchema.safeParse({ name: 'X', extra: true }).success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// CreateContactRequestSchema
// ---------------------------------------------------------------------------

describe('CreateContactRequestSchema', () => {
  it('accepts a minimal payload (email only)', () => {
    expect(CreateContactRequestSchema.safeParse({ email: 'test@example.com' }).success).toBe(true)
  })

  it('rejects an invalid email', () => {
    expect(CreateContactRequestSchema.safeParse({ email: 'not-an-email' }).success).toBe(false)
  })

  it('rejects an empty email', () => {
    expect(CreateContactRequestSchema.safeParse({ email: '' }).success).toBe(false)
  })

  it('accepts an optional companyId as UUID', () => {
    expect(CreateContactRequestSchema.safeParse({ email: 'a@b.com', companyId: VALID_UUID }).success).toBe(true)
  })

  it('rejects unknown fields (strict mode)', () => {
    expect(CreateContactRequestSchema.safeParse({ email: 'a@b.com', unknown: 'x' }).success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// AcqEmailValidSchema / AcqContactStatusSchema / AcqCompanyStatusSchema
// ---------------------------------------------------------------------------

describe('AcqEmailValidSchema', () => {
  it.each(['VALID', 'INVALID', 'RISKY', 'UNKNOWN'])('accepts "%s"', (v) => {
    expect(AcqEmailValidSchema.safeParse(v).success).toBe(true)
  })

  it('rejects lowercase or unknown value', () => {
    expect(AcqEmailValidSchema.safeParse('valid').success).toBe(false)
    expect(AcqEmailValidSchema.safeParse('pending').success).toBe(false)
  })
})

describe('AcqContactStatusSchema', () => {
  it.each(['active', 'invalid'])('accepts "%s"', (s) => {
    expect(AcqContactStatusSchema.safeParse(s).success).toBe(true)
  })

  it('rejects unknown status', () => {
    expect(AcqContactStatusSchema.safeParse('deleted').success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// AcqArtifactOwnerKindSchema
// ---------------------------------------------------------------------------

describe('AcqArtifactOwnerKindSchema', () => {
  it.each(['company', 'contact', 'deal', 'list', 'list_member'])('accepts "%s"', (kind) => {
    expect(AcqArtifactOwnerKindSchema.safeParse(kind).success).toBe(true)
  })

  it('rejects unknown owner kind', () => {
    expect(AcqArtifactOwnerKindSchema.safeParse('organization').success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// ListArtifactsQuerySchema
// ---------------------------------------------------------------------------

describe('ListArtifactsQuerySchema', () => {
  it('accepts a valid query', () => {
    expect(ListArtifactsQuerySchema.safeParse({ ownerKind: 'deal', ownerId: VALID_UUID }).success).toBe(true)
  })

  it('rejects an invalid ownerKind', () => {
    expect(ListArtifactsQuerySchema.safeParse({ ownerKind: 'org', ownerId: VALID_UUID }).success).toBe(false)
  })

  it('rejects a non-UUID ownerId', () => {
    expect(ListArtifactsQuerySchema.safeParse({ ownerKind: 'deal', ownerId: 'not-a-uuid' }).success).toBe(false)
  })

  it('rejects unknown fields (strict mode)', () => {
    expect(ListArtifactsQuerySchema.safeParse({ ownerKind: 'deal', ownerId: VALID_UUID, extra: 'x' }).success).toBe(
      false
    )
  })
})

// ---------------------------------------------------------------------------
// CreateArtifactRequestSchema
// ---------------------------------------------------------------------------

describe('CreateArtifactRequestSchema', () => {
  const valid = {
    ownerKind: 'deal' as const,
    ownerId: VALID_UUID,
    kind: 'proposal',
    content: { url: 'https://example.com' }
  }

  it('accepts a minimal valid payload', () => {
    expect(CreateArtifactRequestSchema.safeParse(valid).success).toBe(true)
  })

  it('accepts an optional sourceExecutionId', () => {
    expect(CreateArtifactRequestSchema.safeParse({ ...valid, sourceExecutionId: VALID_UUID }).success).toBe(true)
  })

  it('rejects a non-UUID sourceExecutionId', () => {
    expect(CreateArtifactRequestSchema.safeParse({ ...valid, sourceExecutionId: 'not-a-uuid' }).success).toBe(false)
  })

  it('rejects an empty kind', () => {
    expect(CreateArtifactRequestSchema.safeParse({ ...valid, kind: '' }).success).toBe(false)
  })

  it('rejects unknown fields (strict mode)', () => {
    expect(CreateArtifactRequestSchema.safeParse({ ...valid, bogus: true }).success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// ListMembersQuerySchema
// ---------------------------------------------------------------------------

describe('ListMembersQuerySchema', () => {
  it('accepts an empty query with defaults', () => {
    const result = ListMembersQuerySchema.safeParse({})
    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.limit).toBe(50)
      expect(result.data.offset).toBe(0)
    }
  })

  it('rejects limit exceeding 500', () => {
    expect(ListMembersQuerySchema.safeParse({ limit: '501' }).success).toBe(false)
  })

  it('rejects limit less than 1', () => {
    expect(ListMembersQuerySchema.safeParse({ limit: '0' }).success).toBe(false)
  })

  it('rejects unknown fields (strict mode)', () => {
    expect(ListMembersQuerySchema.safeParse({ extra: 'x' }).success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// ListReadQuerySchema
// ---------------------------------------------------------------------------

describe('ListReadQuerySchema', () => {
  it('accepts SDK list filters and coerces pagination', () => {
    const result = ListReadQuerySchema.safeParse({
      status: 'launched',
      batch: 'batch-2026-05',
      vertical: 'veterinary',
      limit: '25',
      offset: '50'
    })

    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data).toMatchObject({ limit: 25, offset: 50 })
    }
  })

  it('keeps pagination optional for backward-compatible list reads', () => {
    expect(ListReadQuerySchema.safeParse({}).success).toBe(true)
  })

  it('rejects unknown fields (strict mode)', () => {
    expect(ListReadQuerySchema.safeParse({ includeDeals: true }).success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// GetListQuerySchema
// ---------------------------------------------------------------------------

describe('GetListQuerySchema', () => {
  it('defaults to thin deal refs and omits progress unless requested', () => {
    const result = GetListQuerySchema.safeParse({})

    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data).toEqual({ includeDeals: true, includeProgress: false, dealLimit: 25 })
    }
  })

  it('coerces boolean include flags from query strings', () => {
    const result = GetListQuerySchema.safeParse({
      includeDeals: 'false',
      includeProgress: 'true',
      dealLimit: '10'
    })

    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data).toEqual({ includeDeals: false, includeProgress: true, dealLimit: 10 })
    }
  })

  it('rejects unknown fields (strict mode)', () => {
    expect(GetListQuerySchema.safeParse({ depth: 2 }).success).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// ListRecordsQuerySchema
// ---------------------------------------------------------------------------

describe('ListRecordsQuerySchema', () => {
  it('accepts contact records for DTC decision-maker enrichment', () => {
    const result = ListRecordsQuerySchema.safeParse({
      entity: 'contact',
      stage: 'decision-makers-enriched'
    })

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

  it('keeps company records valid for qualified and uploaded stages', () => {
    expect(ListRecordsQuerySchema.safeParse({ entity: 'company', stage: 'qualified' }).success).toBe(true)
    expect(ListRecordsQuerySchema.safeParse({ entity: 'company', stage: 'uploaded' }).success).toBe(true)
  })

  it('accepts stage/entity combinations at the transport boundary', () => {
    expect(ListRecordsQuerySchema.safeParse({ entity: 'contact', stage: 'qualified' }).success).toBe(true)
  })
})

// ---------------------------------------------------------------------------
// AcqListResponseSchema (forward-compat)
// ---------------------------------------------------------------------------

describe('AcqListResponseSchema (forward-compat)', () => {
  const baseList = {
    id: VALID_UUID,
    organizationId: VALID_UUID,
    name: 'Test List',
    description: null,
    batchIds: [],
    instantlyCampaignId: null,
    status: 'draft' as const,
    metadata: {},
    launchedAt: null,
    completedAt: null,
    createdAt: ISO_TS,
    scrapingConfig: {},
    icp: {},
    pipelineConfig: {}
  }

  it('accepts a valid list response', () => {
    expect(AcqListResponseSchema.safeParse(baseList).success).toBe(true)
  })

  it('accepts extra fields at top level (not strict)', () => {
    expect(AcqListResponseSchema.safeParse({ ...baseList, futureField: 'extra' }).success).toBe(true)
  })
})

// ---------------------------------------------------------------------------
// AcqListDetailResponseSchema
// ---------------------------------------------------------------------------

describe('AcqListDetailResponseSchema', () => {
  const baseList = {
    id: VALID_UUID,
    organizationId: VALID_UUID,
    name: 'Test List',
    description: null,
    batchIds: [],
    instantlyCampaignId: null,
    status: 'draft' as const,
    metadata: {},
    launchedAt: null,
    completedAt: null,
    createdAt: ISO_TS,
    scrapingConfig: {},
    icp: {},
    pipelineConfig: {}
  }

  it('accepts thin lineage deal refs on list detail responses', () => {
    expect(
      AcqListDetailResponseSchema.safeParse({
        ...baseList,
        lineage: {
          deals: {
            total: 1,
            refs: [
              {
                id: VALID_UUID,
                contactEmail: 'lead@example.com',
                stageKey: 'proposal',
                stateKey: null,
                sourceType: 'outreach',
                lastActivityAt: ISO_TS
              }
            ],
            truncated: false
          }
        }
      }).success
    ).toBe(true)
  })

  it('accepts optional progress on list detail responses', () => {
    expect(
      AcqListDetailResponseSchema.safeParse({
        ...baseList,
        progress: {
          totalMembers: 0,
          totalCompanies: 0,
          byCompanyStage: {},
          byContactStage: {}
        }
      }).success
    ).toBe(true)
  })
})

// ---------------------------------------------------------------------------
// AcqListStatusResponseSchema
// ---------------------------------------------------------------------------

describe('AcqListStatusResponseSchema', () => {
  it('accepts a portfolio summary across acquisition lists', () => {
    expect(
      AcqListStatusResponseSchema.safeParse({
        totalLists: 1,
        totalCompanies: 10,
        totalContacts: 5,
        totalDeals: 2,
        byStatus: { launched: 1 },
        lists: [
          {
            listId: VALID_UUID,
            name: 'Pipeline',
            status: 'launched',
            totalCompanies: 10,
            totalContacts: 5,
            totalDeals: 2,
            createdAt: ISO_TS
          }
        ]
      }).success
    ).toBe(true)
  })
})

// ---------------------------------------------------------------------------
// AcqContactResponseSchema (forward-compat)
// ---------------------------------------------------------------------------

describe('AcqContactResponseSchema (forward-compat)', () => {
  const baseContact = {
    id: VALID_UUID,
    organizationId: VALID_UUID,
    companyId: null,
    email: 'alice@example.com',
    emailValid: null,
    firstName: null,
    lastName: null,
    linkedinUrl: null,
    title: null,
    headline: null,
    filterReason: null,
    openingLine: null,
    source: null,
    sourceId: null,
    processingState: null,
    enrichmentData: null,
    attioPersonId: null,
    batchId: null,
    status: 'active' as const,
    createdAt: ISO_TS,
    updatedAt: ISO_TS
  }

  it('accepts a valid contact response', () => {
    expect(AcqContactResponseSchema.safeParse(baseContact).success).toBe(true)
  })

  it('accepts extra fields (not strict)', () => {
    expect(AcqContactResponseSchema.safeParse({ ...baseContact, newField: 'x' }).success).toBe(true)
  })

  it('rejects an invalid emailValid value', () => {
    expect(AcqContactResponseSchema.safeParse({ ...baseContact, emailValid: 'BAD' }).success).toBe(false)
  })
})
