import { describe, expect, it } from 'vitest'
import {
  PoliciesDomainSchema,
  PolicyApplicabilitySchema,
  PolicyEffectSchema,
  PolicyPredicateSchema,
  PolicySchema,
  PolicyTriggerSchema
} from '../../domains/policies'

describe('policies domain', () => {
  it('accepts structured trigger predicate action and applicability contracts', () => {
    const policy = PolicySchema.parse({
      id: 'policy.lead-gen.approval',
      order: 10,
      label: 'Lead Gen Approval',
      trigger: {
        kind: 'action-invocation',
        actionId: 'lead-gen.export.list'
      },
      predicate: {
        kind: 'threshold',
        metric: 'lead-gen.export.count',
        operator: 'gte',
        value: 100
      },
      actions: [{ kind: 'require-approval', roleId: 'role.sales-owner' }],
      appliesTo: {
        systemIds: ['sales.lead-gen'],
        actionIds: ['lead-gen.export.list']
      }
    })

    expect(policy.lifecycle).toBe('active')
    expect(policy.appliesTo.systemIds).toEqual(['sales.lead-gen'])
  })

  it('defaults to an empty policies domain', () => {
    expect(PoliciesDomainSchema.parse({})).toEqual({})
  })

  describe('PolicyEffect discrimination', () => {
    it('parses require-approval with roleId', () => {
      const result = PolicyEffectSchema.parse({ kind: 'require-approval', roleId: 'role.sales-owner' })
      expect(result).toMatchObject({ kind: 'require-approval', roleId: 'role.sales-owner' })
    })

    it('parses require-approval without roleId (roleId is optional)', () => {
      const result = PolicyEffectSchema.parse({ kind: 'require-approval' })
      expect(result).toMatchObject({ kind: 'require-approval' })
      expect((result as { roleId?: unknown }).roleId).toBeUndefined()
    })

    it('parses invoke-action with required actionId', () => {
      const result = PolicyEffectSchema.parse({ kind: 'invoke-action', actionId: 'crm.deal.update' })
      expect(result).toMatchObject({ kind: 'invoke-action', actionId: 'crm.deal.update' })
    })

    it('parses notify-role with required roleId', () => {
      const result = PolicyEffectSchema.parse({ kind: 'notify-role', roleId: 'role.sales-owner' })
      expect(result).toMatchObject({ kind: 'notify-role', roleId: 'role.sales-owner' })
    })

    it('parses block with no extra fields', () => {
      const result = PolicyEffectSchema.parse({ kind: 'block' })
      expect(result).toEqual({ kind: 'block' })
    })

    it('strips extra fields on require-approval (Zod default strip behavior)', () => {
      const result = PolicyEffectSchema.safeParse({
        kind: 'require-approval',
        actionId: 'crm.deal.update'
      })
      expect(result.success).toBe(true)
      if (result.success) {
        expect((result.data as { actionId?: unknown }).actionId).toBeUndefined()
      }
    })

    it('rejects invoke-action without actionId', () => {
      const result = PolicyEffectSchema.safeParse({ kind: 'invoke-action' })
      expect(result.success).toBe(false)
    })

    it('rejects notify-role without roleId', () => {
      const result = PolicyEffectSchema.safeParse({ kind: 'notify-role' })
      expect(result.success).toBe(false)
    })
  })

  describe('PolicyTrigger discrimination', () => {
    it('parses event with eventId in kebab-colon format', () => {
      const result = PolicyTriggerSchema.parse({ kind: 'event', eventId: 'workflow-id:processed' })
      expect(result).toMatchObject({ kind: 'event', eventId: 'workflow-id:processed' })
    })

    it('parses action-invocation with actionId', () => {
      const result = PolicyTriggerSchema.parse({ kind: 'action-invocation', actionId: 'crm.deal.update' })
      expect(result).toMatchObject({ kind: 'action-invocation', actionId: 'crm.deal.update' })
    })

    it('parses schedule with valid short cron string', () => {
      const result = PolicyTriggerSchema.parse({ kind: 'schedule', cron: '0 9 * * *' })
      expect(result).toMatchObject({ kind: 'schedule', cron: '0 9 * * *' })
    })

    it('parses manual with no fields', () => {
      const result = PolicyTriggerSchema.parse({ kind: 'manual' })
      expect(result).toEqual({ kind: 'manual' })
    })

    it('rejects event without eventId', () => {
      const result = PolicyTriggerSchema.safeParse({ kind: 'event' })
      expect(result.success).toBe(false)
    })

    it('rejects schedule with empty cron (fails min(1))', () => {
      const result = PolicyTriggerSchema.safeParse({ kind: 'schedule', cron: '' })
      expect(result.success).toBe(false)
    })

    it('rejects schedule with oversized cron (>120 chars)', () => {
      const result = PolicyTriggerSchema.safeParse({ kind: 'schedule', cron: 'x'.repeat(121) })
      expect(result.success).toBe(false)
    })

    it('rejects action-invocation without actionId', () => {
      const result = PolicyTriggerSchema.safeParse({ kind: 'action-invocation' })
      expect(result.success).toBe(false)
    })
  })

  describe('PolicyPredicate discrimination', () => {
    it('parses always with no fields', () => {
      const result = PolicyPredicateSchema.parse({ kind: 'always' })
      expect(result).toEqual({ kind: 'always' })
    })

    it('parses expression with valid expression string', () => {
      const result = PolicyPredicateSchema.parse({ kind: 'expression', expression: 'deal.value > 10000' })
      expect(result).toMatchObject({ kind: 'expression', expression: 'deal.value > 10000' })
    })

    it('parses threshold with metric, operator, and value', () => {
      const result = PolicyPredicateSchema.parse({
        kind: 'threshold',
        metric: 'lead-gen.export.count',
        operator: 'lt',
        value: 50
      })
      expect(result).toMatchObject({ kind: 'threshold', metric: 'lead-gen.export.count', operator: 'lt', value: 50 })
    })

    it('parses threshold with all operator values', () => {
      for (const operator of ['lt', 'lte', 'eq', 'gte', 'gt'] as const) {
        const result = PolicyPredicateSchema.parse({ kind: 'threshold', metric: 'some.metric', operator, value: 0 })
        expect(result).toMatchObject({ operator })
      }
    })

    it('rejects expression with empty string', () => {
      const result = PolicyPredicateSchema.safeParse({ kind: 'expression', expression: '' })
      expect(result.success).toBe(false)
    })

    it('rejects expression longer than 2000 chars', () => {
      const result = PolicyPredicateSchema.safeParse({ kind: 'expression', expression: 'x'.repeat(2001) })
      expect(result.success).toBe(false)
    })

    it('rejects threshold without operator', () => {
      const result = PolicyPredicateSchema.safeParse({ kind: 'threshold', metric: 'some.metric', value: 10 })
      expect(result.success).toBe(false)
    })

    it('rejects threshold with invalid operator', () => {
      const result = PolicyPredicateSchema.safeParse({
        kind: 'threshold',
        metric: 'some.metric',
        operator: '!=',
        value: 10
      })
      expect(result.success).toBe(false)
    })
  })

  describe('PolicyApplicability scoping', () => {
    it('parses empty object and defaults all four fields to empty arrays', () => {
      const result = PolicyApplicabilitySchema.parse({})
      expect(result).toEqual({ systemIds: [], actionIds: [], resourceIds: [], roleIds: [] })
    })

    it('parses with only systemIds populated, others default to empty', () => {
      const result = PolicyApplicabilitySchema.parse({ systemIds: ['sales.lead-gen'] })
      expect(result).toEqual({ systemIds: ['sales.lead-gen'], actionIds: [], resourceIds: [], roleIds: [] })
    })

    it('parses with all four fields populated', () => {
      const result = PolicyApplicabilitySchema.parse({
        systemIds: ['sales.lead-gen'],
        actionIds: ['crm.deal.update'],
        resourceIds: ['leadgen-workflow'],
        roleIds: ['role.sales-owner']
      })
      expect(result).toEqual({
        systemIds: ['sales.lead-gen'],
        actionIds: ['crm.deal.update'],
        resourceIds: ['leadgen-workflow'],
        roleIds: ['role.sales-owner']
      })
    })

    it('parses with multiple IDs in one field', () => {
      const result = PolicyApplicabilitySchema.parse({ systemIds: ['s1', 's2'] })
      expect(result.systemIds).toEqual(['s1', 's2'])
    })
  })

  describe('Policy lifecycle and order', () => {
    const basePolicy = {
      id: 'policy-a',
      order: 10,
      label: 'Policy A',
      trigger: { kind: 'manual' },
      actions: [{ kind: 'block' }]
    }

    it('defaults lifecycle to active when omitted', () => {
      const result = PolicySchema.parse(basePolicy)
      expect(result.lifecycle).toBe('active')
    })

    it('parses all 5 lifecycle values', () => {
      for (const lifecycle of ['draft', 'beta', 'active', 'deprecated', 'archived'] as const) {
        const result = PolicySchema.parse({ ...basePolicy, lifecycle })
        expect(result.lifecycle).toBe(lifecycle)
      }
    })

    it('rejects invalid lifecycle value', () => {
      const result = PolicySchema.safeParse({ ...basePolicy, lifecycle: 'pending' })
      expect(result.success).toBe(false)
    })

    it('accepts arbitrary order numbers including negative', () => {
      for (const order of [10, 25, -5, 1000]) {
        const result = PolicySchema.parse({ ...basePolicy, order })
        expect(result.order).toBe(order)
      }
    })
  })

  describe('PoliciesDomainSchema key-id consistency', () => {
    const makePolicy = (id: string) => ({
      id,
      order: 10,
      label: 'Test Policy',
      trigger: { kind: 'manual' },
      actions: [{ kind: 'block' }]
    })

    it('parses a single policy where key matches id', () => {
      const result = PoliciesDomainSchema.parse({ 'policy-1': makePolicy('policy-1') })
      expect(result['policy-1']).toMatchObject({ id: 'policy-1' })
    })

    it('parses multiple policies where all keys match their ids', () => {
      const result = PoliciesDomainSchema.parse({
        'policy-1': makePolicy('policy-1'),
        'policy-2': makePolicy('policy-2')
      })
      expect(Object.keys(result)).toHaveLength(2)
      expect(result['policy-2']).toMatchObject({ id: 'policy-2' })
    })

    it('rejects a policy where key does not match id', () => {
      const result = PoliciesDomainSchema.safeParse({ 'key-name': makePolicy('different-id') })
      expect(result.success).toBe(false)
    })
  })

  describe('actions array constraints', () => {
    const basePolicy = {
      id: 'policy-b',
      order: 10,
      label: 'Policy B',
      trigger: { kind: 'manual' }
    }

    it('parses a single effect in the array', () => {
      const result = PolicySchema.parse({ ...basePolicy, actions: [{ kind: 'block' }] })
      expect(result.actions).toHaveLength(1)
    })

    it('parses multiple effects of the same kind', () => {
      const result = PolicySchema.parse({
        ...basePolicy,
        actions: [
          { kind: 'notify-role', roleId: 'role.sales-owner' },
          { kind: 'notify-role', roleId: 'role.ops-lead' }
        ]
      })
      expect(result.actions).toHaveLength(2)
    })

    it('parses multiple effects of different kinds', () => {
      const result = PolicySchema.parse({
        ...basePolicy,
        actions: [
          { kind: 'require-approval', roleId: 'role.sales-owner' },
          { kind: 'invoke-action', actionId: 'crm.deal.update' },
          { kind: 'notify-role', roleId: 'role.ops-lead' }
        ]
      })
      expect(result.actions).toHaveLength(3)
    })

    it('rejects empty actions array (min(1) violation)', () => {
      const result = PolicySchema.safeParse({ ...basePolicy, actions: [] })
      expect(result.success).toBe(false)
    })
  })
})
