import { describe, expect, it } from 'vitest'
import {
  AgentRoleHolderSchema,
  DEFAULT_ORGANIZATION_MODEL_ROLES,
  HumanRoleHolderSchema,
  RoleHolderSchema,
  RoleSchema,
  RolesDomainSchema
} from '../../domains/roles'
import { resolveOrganizationModel } from '../../resolve'

describe('RoleSchema - positive parse', () => {
  it('accepts a fully-populated role', () => {
    const result = RoleSchema.safeParse({
      id: 'role-ceo',
      order: 10,
      title: 'CEO',
      responsibilities: ['Set company direction', 'Hire and develop leadership team'],
      reportsToId: undefined,
      heldBy: { kind: 'human', userId: 'user.alice' },
      responsibleFor: ['sys.platform']
    })

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

  it('accepts a minimal role - only id, order, and title required', () => {
    const result = RoleSchema.safeParse({ id: 'role-minimal', order: 10, title: 'Head of Sales' })

    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.id).toBe('role-minimal')
      expect(result.data.title).toBe('Head of Sales')
      expect(result.data.responsibilities).toEqual([])
      expect(result.data.reportsToId).toBeUndefined()
      expect(result.data.heldBy).toBeUndefined()
      expect(result.data.responsibleFor).toBeUndefined()
    }
  })

  it('accepts human, agent, team, and multi-holder variants', () => {
    expect(
      RoleSchema.safeParse({
        id: 'role-human',
        order: 10,
        title: 'Human',
        heldBy: { kind: 'human', userId: 'user.alice' }
      }).success
    ).toBe(true)
    expect(
      RoleSchema.safeParse({
        id: 'role-agent',
        order: 10,
        title: 'Agent',
        heldBy: { kind: 'agent', agentId: 'command-center-assistant' }
      }).success
    ).toBe(true)
    expect(
      RoleSchema.safeParse({
        id: 'role-team',
        order: 10,
        title: 'Team',
        heldBy: { kind: 'team', memberIds: ['user.alice'] }
      }).success
    ).toBe(true)
    expect(
      RoleSchema.safeParse({
        id: 'role-multi',
        order: 10,
        title: 'Multi',
        heldBy: [
          { kind: 'human', userId: 'user.alice' },
          { kind: 'team', memberIds: ['user.bob', 'user.carol'] }
        ]
      }).success
    ).toBe(true)
  })

  it('trims whitespace from id and title', () => {
    const result = RoleSchema.safeParse({
      id: '  role-trim  ',
      order: 10,
      title: '  Trimmed Title  '
    })

    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.id).toBe('role-trim')
      expect(result.data.title).toBe('Trimmed Title')
    }
  })
})

describe('RoleSchema - default values', () => {
  it('responsibilities defaults to empty array when omitted', () => {
    const result = RoleSchema.safeParse({ id: 'role-defaults', order: 10, title: 'Engineer' })

    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.responsibilities).toEqual([])
    }
  })

  it('reportsToId, heldBy, and responsibleFor are optional', () => {
    const result = RoleSchema.safeParse({ id: 'role-defaults', order: 10, title: 'Engineer' })

    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.reportsToId).toBeUndefined()
      expect(result.data.heldBy).toBeUndefined()
      expect(result.data.responsibleFor).toBeUndefined()
    }
  })
})

describe('RoleSchema - negative parse', () => {
  it('rejects missing or invalid required fields', () => {
    expect(RoleSchema.safeParse({ title: 'No ID Role' }).success).toBe(false)
    expect(RoleSchema.safeParse({ id: '', order: 10, title: 'Empty ID' }).success).toBe(false)
    expect(RoleSchema.safeParse({ id: '   ', order: 10, title: 'Whitespace ID' }).success).toBe(false)
    expect(RoleSchema.safeParse({ id: 'role-no-title', order: 10 }).success).toBe(false)
    expect(RoleSchema.safeParse({ id: 'role-empty-title', order: 10, title: '' }).success).toBe(false)
    expect(RoleSchema.safeParse({ id: 'role-whitespace-title', order: 10, title: '   ' }).success).toBe(false)
  })

  it('rejects responsibilities as a non-array value', () => {
    const result = RoleSchema.safeParse({
      id: 'role-bad-resp',
      order: 10,
      title: 'Bad Role',
      responsibilities: 'single string'
    })

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

  it('rejects legacy string heldBy values and invalid holders', () => {
    expect(
      RoleSchema.safeParse({ id: 'role-legacy-holder', order: 10, title: 'Legacy Holder', heldBy: 'Alice' }).success
    ).toBe(false)
    expect(
      RoleSchema.safeParse({
        id: 'role-empty-team',
        order: 10,
        title: 'Empty Team',
        heldBy: { kind: 'team', memberIds: [] }
      }).success
    ).toBe(false)
    expect(
      RoleSchema.safeParse({ id: 'role-empty-holders', order: 10, title: 'Empty Holders', heldBy: [] }).success
    ).toBe(false)
  })
})

describe('RoleHolderSchema', () => {
  it('accepts human, agent, and team holder variants', () => {
    expect(HumanRoleHolderSchema.safeParse({ kind: 'human', userId: 'user.alice' }).success).toBe(true)
    expect(AgentRoleHolderSchema.safeParse({ kind: 'agent', agentId: 'command-center-assistant' }).success).toBe(true)
    expect(RoleHolderSchema.safeParse({ kind: 'team', memberIds: ['user.alice'] }).success).toBe(true)
  })

  it('rejects unknown holder kinds', () => {
    expect(RoleHolderSchema.safeParse({ kind: 'contractor', userId: 'user.alice' }).success).toBe(false)
  })
})

describe('RoleSchema - plain-language field names (no EOS jargon)', () => {
  it('parsed role has the plain-language keys title / responsibilities / reportsToId / heldBy / responsibleFor', () => {
    const result = RoleSchema.safeParse({
      id: 'role-plain',
      order: 10,
      title: 'Product Lead',
      responsibilities: ['Own roadmap'],
      reportsToId: 'role-ceo',
      heldBy: { kind: 'human', userId: 'user.dave' },
      responsibleFor: ['sys.product']
    })

    expect(result.success).toBe(true)
    if (result.success) {
      const keys = Object.keys(result.data)
      expect(keys).toContain('title')
      expect(keys).toContain('responsibilities')
      expect(keys).toContain('reportsToId')
      expect(keys).toContain('heldBy')
      expect(keys).toContain('responsibleFor')
      expect(keys).not.toContain('seatTitle')
      expect(keys).not.toContain('accountabilities')
      expect(keys).not.toContain('seat')
      expect(keys).not.toContain('seats')
    }
  })
})

describe('RolesDomainSchema - structural', () => {
  it('accepts an empty roles record', () => {
    expect(RolesDomainSchema.safeParse({}).success).toBe(true)
  })

  it('defaults roles to empty record when omitted', () => {
    const result = RolesDomainSchema.safeParse({})

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

  it('accepts multiple valid roles', () => {
    const result = RolesDomainSchema.safeParse({
      'role-a': { id: 'role-a', order: 10, title: 'CEO' },
      'role-b': { id: 'role-b', order: 20, title: 'COO', reportsToId: 'role-a' }
    })

    expect(result.success).toBe(true)
    if (result.success) {
      expect(Object.keys(result.data)).toHaveLength(2)
    }
  })

  it('rejects a record where entry id does not match map key', () => {
    expect(
      RolesDomainSchema.safeParse({
        'role-a': { id: 'role-b', order: 10, title: 'Mismatched' }
      }).success
    ).toBe(false)
  })

  it('DEFAULT_ORGANIZATION_MODEL_ROLES constant matches schema parse of empty object', () => {
    const result = RolesDomainSchema.safeParse({})

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

describe('resolveOrganizationModel - roles cross-ref (reportsToId)', () => {
  it('passes when reportsToId references a declared role id', () => {
    expect(() =>
      resolveOrganizationModel({
        roles: {
          'role-ceo': { id: 'role-ceo', order: 10, title: 'CEO' },
          'role-coo': { id: 'role-coo', order: 20, title: 'COO', reportsToId: 'role-ceo' }
        }
      })
    ).not.toThrow()
  })

  it('throws when reportsToId references a non-existent role', () => {
    expect(() =>
      resolveOrganizationModel({
        roles: {
          'role-orphan': { id: 'role-orphan', order: 10, title: 'Orphan Role', reportsToId: 'nonexistent-role' }
        }
      })
    ).toThrow()
  })

  it('throws with a message referencing the unknown reportsToId', () => {
    expect(() =>
      resolveOrganizationModel({
        roles: {
          'role-bad-ref': { id: 'role-bad-ref', order: 10, title: 'Bad Ref Role', reportsToId: 'ghost-role' }
        }
      })
    ).toThrow(/ghost-role/)
  })

  it('passes when reportsToId is absent or roles record is empty', () => {
    expect(() =>
      resolveOrganizationModel({
        roles: {
          'role-top': { id: 'role-top', order: 10, title: 'Top Level Role' }
        }
      })
    ).not.toThrow()
    expect(() => resolveOrganizationModel({ roles: {} })).not.toThrow()
  })

  it('throws when reportsToId points to the same role', () => {
    expect(() =>
      resolveOrganizationModel({
        roles: {
          'role-loop': { id: 'role-loop', order: 10, title: 'Loop Role', reportsToId: 'role-loop' }
        }
      })
    ).toThrow(/reportsToId cycle/)
  })

  it('throws when reportsToId creates an indirect cycle', () => {
    expect(() =>
      resolveOrganizationModel({
        roles: {
          'role-a': { id: 'role-a', order: 10, title: 'Role A', reportsToId: 'role-c' },
          'role-b': { id: 'role-b', order: 20, title: 'Role B', reportsToId: 'role-a' },
          'role-c': { id: 'role-c', order: 30, title: 'Role C', reportsToId: 'role-b' }
        }
      })
    ).toThrow(/reportsToId cycle/)
  })
})

describe('resolveOrganizationModel - roles cross-ref (responsibleFor and heldBy)', () => {
  const validSystem = {
    id: 'sys.platform',
    order: 10,
    title: 'Platform',
    description: 'Owns platform operations.',
    kind: 'platform' as const,
    status: 'active' as const
  }

  const validAgentResource = {
    id: 'command-center-assistant',
    order: 10,
    kind: 'agent' as const,
    systemPath: 'sys.platform',
    status: 'active' as const,
    agentKind: 'platform' as const,
    sessionCapable: true
  }

  it('passes when responsibleFor references a declared system id', () => {
    expect(() =>
      resolveOrganizationModel({
        systems: { 'sys.platform': validSystem },
        roles: {
          'role.platform-owner': {
            id: 'role.platform-owner',
            order: 10,
            title: 'Platform Owner',
            responsibleFor: ['sys.platform']
          }
        }
      })
    ).not.toThrow()
  })

  it('throws when responsibleFor references an unknown system', () => {
    expect(() =>
      resolveOrganizationModel({
        roles: {
          'role.platform-owner': {
            id: 'role.platform-owner',
            order: 10,
            title: 'Platform Owner',
            responsibleFor: ['sys.missing']
          }
        }
      })
    ).toThrow(/unknown responsibleFor system/)
  })

  it('passes when an agent holder references a declared agent resource', () => {
    expect(() =>
      resolveOrganizationModel({
        systems: { 'sys.platform': validSystem },
        resources: {
          'command-center-assistant': validAgentResource
        },
        roles: {
          'role.platform-assistant': {
            id: 'role.platform-assistant',
            order: 10,
            title: 'Platform Assistant',
            heldBy: { kind: 'agent', agentId: 'command-center-assistant' }
          }
        }
      })
    ).not.toThrow()
  })

  it('throws when an agent holder references an unknown resource', () => {
    expect(() =>
      resolveOrganizationModel({
        roles: {
          'role.platform-assistant': {
            id: 'role.platform-assistant',
            order: 10,
            title: 'Platform Assistant',
            heldBy: { kind: 'agent', agentId: 'missing-agent' }
          }
        }
      })
    ).toThrow(/unknown agent holder resource/)
  })

  it('throws when an agent holder references a non-agent resource', () => {
    expect(() =>
      resolveOrganizationModel({
        systems: { 'sys.platform': validSystem },
        resources: {
          'LGN-01-company-scrape': {
            id: 'LGN-01-company-scrape',
            order: 10,
            kind: 'workflow' as const,
            systemPath: 'sys.platform',
            status: 'active' as const
          }
        },
        roles: {
          'role.platform-assistant': {
            id: 'role.platform-assistant',
            order: 10,
            title: 'Platform Assistant',
            heldBy: { kind: 'agent', agentId: 'LGN-01-company-scrape' }
          }
        }
      })
    ).toThrow(/must reference an agent resource/)
  })
})

describe('resolveOrganizationModel - roles domain integration', () => {
  it('merges partial roles override and preserves empty roles default', () => {
    const model = resolveOrganizationModel({ roles: {} })
    expect(Object.keys(model.roles)).toHaveLength(0)
  })

  it('omitting roles key entirely resolves to default empty roles', () => {
    const model = resolveOrganizationModel({})
    expect(model.roles).toBeDefined()
    expect(Object.keys(model.roles)).toHaveLength(0)
  })

  it('does not bleed roles changes into other top-level domains', () => {
    const model = resolveOrganizationModel({
      roles: {
        'role-ceo': { id: 'role-ceo', order: 10, title: 'CEO' }
      }
    })

    expect(model.identity).toBeDefined()
    expect(model.customers).toBeDefined()
    expect(model.offerings).toBeDefined()
    expect(model.systems).toBeDefined()
    // Phase 4 (D8): model.navigation removed from top-level OM.
    expect(model.navigation).toBeDefined()
    expect(model.knowledge).toBeDefined()
  })

  it('resolved model includes roles domain alongside other new domains', () => {
    const model = resolveOrganizationModel({
      customers: {
        'seg-a': { id: 'seg-a', order: 10, name: 'Segment A' }
      },
      roles: {
        'role-ceo': {
          id: 'role-ceo',
          order: 10,
          title: 'CEO',
          heldBy: { kind: 'human', userId: 'user.alice' }
        }
      }
    })

    expect(Object.keys(model.customers)).toHaveLength(1)
    expect(Object.keys(model.roles)).toHaveLength(1)
    expect(model.roles['role-ceo'].heldBy).toEqual({ kind: 'human', userId: 'user.alice' })
  })
})
