import { describe, it, expect } from 'vitest'
import {
  evaluateCrmDealNextAction as evaluateCrmDealNextActionCore,
  resolveCrmNextActionRuleConfig
} from './crm-next-action'
import type { CrmNextActionRuleConfig } 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 ELEVASIS_CRM_NEXT_ACTION_RULE_CONFIG: CrmNextActionRuleConfig = {
  mappings: [
    { stateKey: 'discovery_link_sent', ownership: 'them', actionKey: 'send_nudge', requiresStale: true },
    { stateKey: 'discovery_replied', ownership: 'us', actionKey: 'send_reply' },
    { stateKey: 'discovery_nudging', ownership: 'them', actionKey: 'send_nudge' }
  ],
  ownershipUsFallback: 'send_reply'
}

function evaluateCrmDealNextAction(
  input: Parameters<typeof evaluateCrmDealNextActionCore>[0],
  options: Parameters<typeof evaluateCrmDealNextActionCore>[1] = {}
): string | null {
  return evaluateCrmDealNextActionCore(input, {
    config: ELEVASIS_CRM_NEXT_ACTION_RULE_CONFIG,
    ...options
  })
}

// ---------------------------------------------------------------------------
// Closed deals → null
// ---------------------------------------------------------------------------

describe('evaluateCrmDealNextAction — closed deals', () => {
  it('returns null for closed_won regardless of activity', () => {
    expect(
      evaluateCrmDealNextAction({
        stage_key: 'interested',
        state_key: 'closed_won',
        activity_log: [makeEvent(INBOUND, '2026-01-10T10:00:00Z')]
      })
    ).toBeNull()
  })

  it('returns null for closed_lost regardless of activity', () => {
    expect(
      evaluateCrmDealNextAction({
        stage_key: 'interested',
        state_key: 'closed_lost',
        activity_log: [makeEvent(INBOUND, '2026-01-10T10:00:00Z')]
      })
    ).toBeNull()
  })

  it('returns null for closed stages even when state_key is still actionable', () => {
    expect(
      evaluateCrmDealNextAction({
        stage_key: 'closed_won',
        state_key: 'discovery_replied',
        activity_log: [makeEvent(INBOUND, '2026-01-10T10:00:00Z')]
      })
    ).toBeNull()
  })

  it('returns null when activity_log is empty (ownership null)', () => {
    expect(
      evaluateCrmDealNextAction({
        stage_key: 'interested',
        state_key: 'discovery_replied',
        activity_log: []
      })
    ).toBeNull()
  })
})

// ---------------------------------------------------------------------------
// (state_key, ownership) mapping table
// ---------------------------------------------------------------------------

describe('evaluateCrmDealNextAction — mapping table', () => {
  it('(discovery_replied, us) → send_reply', () => {
    const result = evaluateCrmDealNextAction({
      stage_key: 'interested',
      state_key: 'discovery_replied',
      activity_log: [makeEvent(INBOUND, '2026-04-20T10:00:00Z')]
    })
    expect(result).toBe('send_reply')
  })

  it('(discovery_nudging, them) → send_nudge', () => {
    const result = evaluateCrmDealNextAction({
      stage_key: 'interested',
      state_key: 'discovery_nudging',
      activity_log: [makeEvent(OUTBOUND, '2026-04-20T10:00:00Z')]
    })
    expect(result).toBe('send_nudge')
  })

  it('(discovery_link_sent, them) → send_nudge only after staleAfterDays', () => {
    const recentActivity = '2026-04-29T10:00:00Z'
    const now = new Date('2026-04-30T10:00:00Z')

    // Age = 1 day — below staleAfterDays (14), no mapping fires → null fallback
    // ownership === 'them' and no stale match → falls through to null
    const notStale = evaluateCrmDealNextAction(
      {
        stage_key: 'interested',
        state_key: 'discovery_link_sent',
        activity_log: [makeEvent(OUTBOUND, recentActivity)]
      },
      { now }
    )
    expect(notStale).toBeNull()
  })

  it('(discovery_link_sent, them) → send_nudge when age >= staleAfterDays', () => {
    const oldActivity = '2026-04-01T10:00:00Z'
    const now = new Date('2026-04-30T10:00:00Z') // 29 days later

    const stale = evaluateCrmDealNextAction(
      {
        stage_key: 'interested',
        state_key: 'discovery_link_sent',
        activity_log: [makeEvent(OUTBOUND, oldActivity)]
      },
      { now }
    )
    expect(stale).toBe('send_nudge')
  })

  it('(*, us) → ownershipUsFallback send_reply when no explicit mapping matches', () => {
    // reply_sent + inbound → ownership=us, state_key not in explicit mappings
    const result = evaluateCrmDealNextAction({
      stage_key: 'interested',
      state_key: 'reply_sent',
      activity_log: [makeEvent(INBOUND, '2026-04-20T10:00:00Z')]
    })
    expect(result).toBe('send_reply')
  })

  it('(followup_1_sent, us) → send_reply via fallback', () => {
    const result = evaluateCrmDealNextAction({
      stage_key: 'interested',
      state_key: 'followup_1_sent',
      activity_log: [makeEvent(INBOUND, '2026-04-20T10:00:00Z')]
    })
    expect(result).toBe('send_reply')
  })

  it('returns null when ownership is them and no mapping matches and not stale', () => {
    const result = evaluateCrmDealNextAction({
      stage_key: 'interested',
      state_key: 'followup_2_sent',
      activity_log: [makeEvent(OUTBOUND, '2026-04-29T10:00:00Z')]
    })
    expect(result).toBeNull()
  })
})

// ---------------------------------------------------------------------------
// ageDays option
// ---------------------------------------------------------------------------

describe('evaluateCrmDealNextAction — ageDays option', () => {
  it('uses supplied ageDays instead of computing from activity_log', () => {
    // discovery_link_sent + them + ageDays=20 (>=14) → send_nudge
    const result = evaluateCrmDealNextAction(
      {
        stage_key: 'interested',
        state_key: 'discovery_link_sent',
        activity_log: [makeEvent(OUTBOUND, '2026-04-29T10:00:00Z')]
      },
      { ageDays: 20 }
    )
    expect(result).toBe('send_nudge')
  })

  it('requiresStale blocks when ageDays=0', () => {
    const result = evaluateCrmDealNextAction(
      {
        stage_key: 'interested',
        state_key: 'discovery_link_sent',
        activity_log: [makeEvent(OUTBOUND, '2026-04-29T10:00:00Z')]
      },
      { ageDays: 0 }
    )
    expect(result).toBeNull()
  })
})

// ---------------------------------------------------------------------------
// resolveCrmNextActionRuleConfig — defaults
// ---------------------------------------------------------------------------

describe('resolveCrmNextActionRuleConfig — defaults', () => {
  it('returns generic empty config when called with no input', () => {
    const config = resolveCrmNextActionRuleConfig()
    expect(config.ownershipUsFallback).toBeNull()
    expect(config.mappings).toEqual([])
  })

  it('returns generic empty config for invalid input', () => {
    const config = resolveCrmNextActionRuleConfig('not-an-object')
    expect(config.ownershipUsFallback).toBeNull()
    expect(config.mappings).toEqual([])
  })

  it('accepts a full CrmNextActionRuleConfig directly', () => {
    const full = {
      mappings: [{ stateKey: 'foo', ownership: 'us' as const, actionKey: 'bar' }],
      ownershipUsFallback: 'bar'
    }
    const config = resolveCrmNextActionRuleConfig(full)
    expect(config.ownershipUsFallback).toBe('bar')
    expect(config.mappings).toHaveLength(1)
  })
})

// ---------------------------------------------------------------------------
// Per-org override merging
// ---------------------------------------------------------------------------

describe('resolveCrmNextActionRuleConfig — per-org override', () => {
  it('uses org mappings without merging tenant defaults from core', () => {
    const orgConfig = {
      crm: {
        next_actions: {
          mappings: [{ stateKey: 'discovery_replied', ownership: 'us' as const, actionKey: 'custom_action' }]
        }
      }
    }
    const config = resolveCrmNextActionRuleConfig(orgConfig)
    expect(config.mappings[0].actionKey).toBe('custom_action')
    expect(config.mappings).toHaveLength(1)
  })

  it('overrides ownershipUsFallback when supplied', () => {
    const orgConfig = {
      crm: {
        next_actions: {
          ownershipUsFallback: 'org_custom_reply'
        }
      }
    }
    const config = resolveCrmNextActionRuleConfig(orgConfig)
    expect(config.ownershipUsFallback).toBe('org_custom_reply')
  })

  it('org override fires over default for same (state_key, ownership) pair', () => {
    // Org remaps (discovery_replied, us) → 'org_reply'
    const result = evaluateCrmDealNextAction(
      {
        stage_key: 'interested',
        state_key: 'discovery_replied',
        activity_log: [makeEvent(INBOUND, '2026-04-20T10:00:00Z')]
      },
      {
        config: {
          crm: {
            next_actions: {
              mappings: [{ stateKey: 'discovery_replied', ownership: 'us', actionKey: 'org_reply' }]
            }
          }
        }
      }
    )
    expect(result).toBe('org_reply')
  })

  it('falls back to generic empty config when org override is empty object', () => {
    const config = resolveCrmNextActionRuleConfig({ crm: { next_actions: {} } })
    expect(config.mappings).toEqual([])
    expect(config.ownershipUsFallback).toBeNull()
  })
})
