import { describe, it, expect } from 'vitest'
import {
  evaluateCrmDealPriority as evaluateCrmDealPriorityCore,
  resolveCrmPriorityRuleConfig
} from './crm-priority'
import type { CrmPriorityRuleConfig } from '../../organization-model/domains/sales'

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

function makeEvent(type: string, timestamp: string): Record<string, unknown> {
  return { type, timestamp }
}

const INBOUND = 'reply_received'
const OUTBOUND = 'reply_sent_to_lead'
const NOW = '2026-04-30T12:00:00.000Z'
const ELEVASIS_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
}

function makeDeal(
  overrides: Partial<{
    stage_key: string | null
    state_key: string | null
    activity_log: unknown[]
    updated_at: string
    created_at: string
  }> = {}
) {
  return {
    stage_key: overrides.stage_key ?? 'interested',
    state_key: overrides.state_key ?? 'discovery_replied',
    activity_log: overrides.activity_log ?? [],
    updated_at: overrides.updated_at ?? NOW,
    created_at: overrides.created_at ?? NOW
  }
}

function evaluateCrmDealPriority(
  input: Parameters<typeof evaluateCrmDealPriorityCore>[0],
  options: Parameters<typeof evaluateCrmDealPriorityCore>[1] = {}
) {
  return evaluateCrmDealPriorityCore(input, {
    config: ELEVASIS_CRM_PRIORITY_RULE_CONFIG,
    ...options
  })
}

// ---------------------------------------------------------------------------
// Closed deals → closed_low
// ---------------------------------------------------------------------------

describe('evaluateCrmDealPriority — closed deals', () => {
  it('returns closed_low for closed_won stage', () => {
    const result = evaluateCrmDealPriority(makeDeal({ stage_key: 'closed_won' }), { now: NOW })
    expect(result.bucketKey).toBe('closed_low')
  })

  it('returns closed_low for closed_lost stage', () => {
    const result = evaluateCrmDealPriority(makeDeal({ stage_key: 'closed_lost' }), { now: NOW })
    expect(result.bucketKey).toBe('closed_low')
  })
})

// ---------------------------------------------------------------------------
// needs_response — ownership === 'us'
// ---------------------------------------------------------------------------

describe('evaluateCrmDealPriority — needs_response (ownership === us)', () => {
  it('returns needs_response when lead replied last (inbound after outbound)', () => {
    const result = evaluateCrmDealPriority(
      makeDeal({
        state_key: 'discovery_replied',
        activity_log: [makeEvent(OUTBOUND, '2026-04-28T10:00:00Z'), makeEvent(INBOUND, '2026-04-29T10:00:00Z')]
      }),
      { now: NOW }
    )
    expect(result.bucketKey).toBe('needs_response')
  })

  it('returns needs_response when only inbound event present', () => {
    const result = evaluateCrmDealPriority(
      makeDeal({
        state_key: 'discovery_replied',
        activity_log: [makeEvent(INBOUND, '2026-04-29T10:00:00Z')]
      }),
      { now: NOW }
    )
    expect(result.bucketKey).toBe('needs_response')
  })

  it('returns needs_response regardless of state_key when ownership is us', () => {
    // reply_sent is normally a "we acted" state, but if a new inbound arrives ownership flips
    const result = evaluateCrmDealPriority(
      makeDeal({
        state_key: 'reply_sent',
        activity_log: [makeEvent(OUTBOUND, '2026-04-27T10:00:00Z'), makeEvent(INBOUND, '2026-04-29T10:00:00Z')]
      }),
      { now: NOW }
    )
    expect(result.bucketKey).toBe('needs_response')
  })
})

// ---------------------------------------------------------------------------
// waiting — ownership === 'them' AND age < staleAfterDays
// ---------------------------------------------------------------------------

describe('evaluateCrmDealPriority — waiting (ownership === them, not stale)', () => {
  it('returns waiting when we replied last and age is below staleAfterDays', () => {
    const result = evaluateCrmDealPriority(
      makeDeal({
        state_key: 'reply_sent',
        // No followUpAfterDaysByStateKey entry for reply_sent within window
        activity_log: [makeEvent(OUTBOUND, '2026-04-29T10:00:00Z')]
      }),
      { now: NOW }
    )
    // Age = 1 day, staleAfterDays = 14 → waiting
    // reply_sent has followUpAfterDaysByStateKey=3, nextActionAt is 2026-05-02, not yet due
    expect(result.bucketKey).toBe('waiting')
  })

  it('returns follow_up_due when follow-up window elapses (ownership them)', () => {
    // discovery_link_sent: followUpAfterDaysByStateKey = 3
    // Activity at 2026-04-26, now = 2026-04-30 → age 4 days → nextActionAt = 2026-04-29 (past)
    // updated_at set before activity so latestActivityAt comes from activity_log
    const result = evaluateCrmDealPriority(
      makeDeal({
        state_key: 'discovery_link_sent',
        activity_log: [makeEvent(OUTBOUND, '2026-04-26T10:00:00Z')],
        updated_at: '2026-04-25T00:00:00Z',
        created_at: '2026-04-25T00:00:00Z'
      }),
      { now: NOW }
    )
    expect(result.bucketKey).toBe('follow_up_due')
  })
})

// ---------------------------------------------------------------------------
// stale — ownership === 'them' AND age >= staleAfterDays
// ---------------------------------------------------------------------------

describe('evaluateCrmDealPriority — stale (ownership them, age >= staleAfterDays)', () => {
  it('returns stale when we sent last and age >= staleAfterDays', () => {
    // Activity at 2026-04-10, now = 2026-04-30 → age 20 days >= staleAfterDays 14
    // updated_at set before activity so latestActivityAt comes from activity_log
    const result = evaluateCrmDealPriority(
      makeDeal({
        state_key: 'reply_sent',
        activity_log: [makeEvent(OUTBOUND, '2026-04-10T10:00:00Z')],
        updated_at: '2026-04-09T00:00:00Z',
        created_at: '2026-04-09T00:00:00Z'
      }),
      { now: NOW }
    )
    expect(result.bucketKey).toBe('stale')
  })
})

// ---------------------------------------------------------------------------
// No activity (ownership null)
// ---------------------------------------------------------------------------

describe('evaluateCrmDealPriority — no ownership signal', () => {
  it('returns waiting when no activity log', () => {
    const result = evaluateCrmDealPriority(makeDeal({ activity_log: [] }), { now: NOW })
    expect(result.bucketKey).toBe('waiting')
  })

  it('returns stale when no activity and updated_at is very old', () => {
    const result = evaluateCrmDealPriority(
      {
        stage_key: 'interested',
        state_key: 'discovery_replied',
        activity_log: [],
        updated_at: '2026-01-01T00:00:00Z',
        created_at: '2026-01-01T00:00:00Z'
      },
      { now: NOW }
    )
    expect(result.bucketKey).toBe('stale')
  })
})

// ---------------------------------------------------------------------------
// disabled config
// ---------------------------------------------------------------------------

describe('evaluateCrmDealPriority — disabled config', () => {
  it('returns waiting when config.enabled is false', () => {
    const config = resolveCrmPriorityRuleConfig({ enabled: false })
    const result = evaluateCrmDealPriority(makeDeal({ activity_log: [makeEvent(INBOUND, '2026-04-29T10:00:00Z')] }), {
      now: NOW,
      config
    })
    expect(result.bucketKey).toBe('waiting')
  })
})

// ---------------------------------------------------------------------------
// resolveCrmPriorityRuleConfig — override merging
// ---------------------------------------------------------------------------

describe('resolveCrmPriorityRuleConfig — override merging', () => {
  it('merges staleAfterDays and closedStageKeys from org override', () => {
    const config = resolveCrmPriorityRuleConfig({
      crm: {
        priority: {
          staleAfterDays: 7,
          closedStageKeys: ['custom_closed']
        }
      }
    })
    expect(config.staleAfterDays).toBe(7)
    expect(config.closedStageKeys).toEqual(['custom_closed'])
  })

  it('falls back to defaults for invalid input', () => {
    const config = resolveCrmPriorityRuleConfig('not-valid')
    expect(config.staleAfterDays).toBe(14)
    expect(config.enabled).toBe(true)
  })

  it('does not have needsResponseStateKeys or needsResponseActivityTypes', () => {
    const config = resolveCrmPriorityRuleConfig()
    expect(config).not.toHaveProperty('needsResponseStateKeys')
    expect(config).not.toHaveProperty('needsResponseActivityTypes')
  })
})
