import { describe, expect, it } from 'vitest'
import { isReturnTransitionError } from '../errors.js'
import { applyReturnEvents, decideReturn } from './machine.js'
import type { ReturnState } from './types.js'

const ORDER_ID = 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'
const ACTOR = { userId: '33333333-3333-4333-8333-333333333333' }

function baseState(status: ReturnState['status']): ReturnState {
  return { status, orderId: ORDER_ID, lines: [] }
}

describe('decideReturn', () => {
  it('legal chain: request → approve → received → refunded', () => {
    let status: ReturnState['status'] = 'requested'

    status = applyReturnEvents(
      status,
      decideReturn(baseState(status), { type: 'vendor.approve', actor: ACTOR }),
    )
    expect(status).toBe('approved')

    status = applyReturnEvents(
      status,
      decideReturn(baseState(status), {
        type: 'admin.override',
        to: 'received',
        actor: ACTOR,
      }),
    )
    expect(status).toBe('received')

    status = applyReturnEvents(
      status,
      decideReturn(baseState(status), {
        type: 'admin.override',
        to: 'refunded',
        actor: ACTOR,
      }),
    )
    expect(status).toBe('refunded')
  })

  it('illegal approve from refunded → ReturnTransitionError', () => {
    expect(() =>
      decideReturn(baseState('refunded'), { type: 'vendor.approve', actor: ACTOR }),
    ).toThrowError(expect.objectContaining({ name: 'ReturnTransitionError' }))

    try {
      decideReturn(baseState('refunded'), { type: 'vendor.approve', actor: ACTOR })
    } catch (e) {
      expect(isReturnTransitionError(e)).toBe(true)
    }
  })

  it('admin.override jumps approved to in_transit', () => {
    const events = decideReturn(baseState('approved'), {
      type: 'admin.override',
      to: 'in_transit',
      actor: { isAdmin: true },
    })
    expect(applyReturnEvents('approved', events)).toBe('in_transit')
  })

  it('carrier.received from in_transit → received', () => {
    const events = decideReturn(baseState('in_transit'), {
      type: 'carrier.received',
      actor: ACTOR,
    })
    expect(applyReturnEvents('in_transit', events)).toBe('received')
  })

  it('admin.override→refunded emits canonical return.refunded from approved, in_transit, received', () => {
    // Assert the EVENT ARRAY, not the derived status: applyReturnEvents maps both
    // return.refunded AND return.overridden{to:'refunded'} to status 'refunded',
    // so a status-based check passes even with the old defective event. The
    // canonical settlement event is return.refunded regardless of which
    // non-terminal state the admin overrides from — never return.overridden.
    for (const from of ['approved', 'in_transit', 'received'] as const) {
      expect(
        decideReturn(baseState(from), {
          type: 'admin.override',
          to: 'refunded',
          actor: { isAdmin: true },
        }),
      ).toEqual([{ type: 'return.refunded' }])
    }
  })

  it('admin.override to a non-refunded target still emits return.overridden{to}', () => {
    expect(
      decideReturn(baseState('approved'), {
        type: 'admin.override',
        to: 'rejected',
        actor: { isAdmin: true },
      }),
    ).toEqual([{ type: 'return.overridden', to: 'rejected' }])

    expect(
      decideReturn(baseState('in_transit'), {
        type: 'admin.override',
        to: 'rejected',
        actor: { isAdmin: true },
      }),
    ).toEqual([{ type: 'return.overridden', to: 'rejected' }])
  })

  it('buyer.request is wrapper-owned: pure decider THROWS, never silently returns []', () => {
    // The spec marks buyer.request as wrapper-owned (it creates the return row);
    // the pure decider has no initial state to transition FROM, so every state
    // must reject it with a throw — never an empty event list (which a caller
    // could mistake for a legal no-op transition).
    for (const status of ['requested', 'approved', 'in_transit', 'received'] as const) {
      expect(() =>
        decideReturn(baseState(status), { type: 'buyer.request', actor: ACTOR }),
      ).toThrowError(expect.objectContaining({ name: 'ReturnTransitionError' }))
    }
  })
})
