import { describe, expect, it, vi } from 'vitest'
import { issueInvoice } from '../index.js'
import { createDocumentSpec, createTestDb } from '../test-fixture.js'
import { MorningProvider, type MorningCredentials } from './index.js'

function createCredentials(): MorningCredentials {
  return {
    apiUser: 'api-user@example.test',
    apiPass: 'super-secret',
    companyId: 'company-123',
  }
}

describe('invoicing/morning', () => {
  it('exchanges a token first and uses the bearer token on the create-document call', async () => {
    const fetchImpl = vi
      .fn<typeof fetch>()
      .mockResolvedValueOnce(
        new Response(JSON.stringify({ access_token: 'token-abc' }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        }),
      )
      .mockResolvedValueOnce(
        new Response(
          JSON.stringify({
            id: 'doc-1',
            docNum: 'INV-1',
            url: 'https://morning.test/docs/doc-1',
          }),
          {
            status: 200,
            headers: { 'Content-Type': 'application/json' },
          },
        ),
      )
    const provider = new MorningProvider({
      fetch: fetchImpl,
      baseUrl: 'https://morning.test/v2',
      tokenPath: '/token',
      documentPath: '/doc',
    })

    await provider.issue(createCredentials(), createDocumentSpec('morning-bearer'))

    expect(fetchImpl).toHaveBeenCalledTimes(2)

    const tokenCall = fetchImpl.mock.calls[0]
    expect(tokenCall).toBeDefined()
    const tokenUrl = tokenCall?.[0]
    const tokenInit = tokenCall?.[1]
    expect(tokenUrl).toBe('https://morning.test/v2/token')
    expect(tokenInit).toMatchObject({
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Accept: 'application/json',
      },
    })
    expect(JSON.parse(String(tokenInit?.body))).toEqual(createCredentials())

    const documentCall = fetchImpl.mock.calls[1]
    expect(documentCall).toBeDefined()
    const docUrl = documentCall?.[0]
    const docInit = documentCall?.[1]
    expect(docUrl).toBe('https://morning.test/v2/doc')
    expect(docInit?.headers).toMatchObject({
      Authorization: 'Bearer token-abc',
      'x-company-id': 'company-123',
      'Content-Type': 'application/json',
      Accept: 'application/json',
    })
    expect(JSON.parse(String(docInit?.body))).toMatchObject({
      income: [
        {
          taxTreatment: 'exclusive',
          vatRateBasisPoints: 1800,
        },
      ],
    })
  })

  it('maps a successful Morning response to the invoicing document result', async () => {
    const fetchImpl = vi
      .fn<typeof fetch>()
      .mockResolvedValueOnce(
        new Response(JSON.stringify({ token: 'token-xyz' }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        }),
      )
      .mockResolvedValueOnce(
        new Response(
          JSON.stringify({
            doc_id: 77,
            doc_number: 9001,
            documentUrl: 'https://morning.test/docs/77.pdf',
          }),
          {
            status: 200,
            headers: { 'Content-Type': 'application/json' },
          },
        ),
      )
    const provider = new MorningProvider({
      fetch: fetchImpl,
      baseUrl: 'https://morning.test/v2',
    })

    const result = await provider.issue(createCredentials(), createDocumentSpec('morning-success'))

    expect(result).toEqual({
      documentId: '77',
      documentNumber: '9001',
      documentUrl: 'https://morning.test/docs/77.pdf',
    })
  })

  it('returns a typed error when Morning rejects the create-document request', async () => {
    const db = await createTestDb()
    const fetchImpl = vi
      .fn<typeof fetch>()
      .mockResolvedValueOnce(
        new Response(JSON.stringify({ accessToken: 'token-error' }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
        }),
      )
      .mockResolvedValueOnce(
        new Response(JSON.stringify({ error: { message: 'document rejected' } }), {
          status: 422,
          headers: { 'Content-Type': 'application/json' },
        }),
      )
    const provider = new MorningProvider({
      fetch: fetchImpl,
      baseUrl: 'https://morning.test/v2',
    })

    const result = await issueInvoice(
      db,
      provider,
      createCredentials(),
      createDocumentSpec('morning-api-error'),
    )

    expect(result).toEqual({
      ok: false,
      error: {
        code: 'PROVIDER_REJECTED',
        message: 'Morning create-document failed: document rejected',
      },
    })
  })

  it('enforces the Morning credential shape before making network calls', async () => {
    const fetchImpl = vi.fn<typeof fetch>()
    const provider = new MorningProvider({
      fetch: fetchImpl,
      baseUrl: 'https://morning.test/v2',
    })

    await expect(
      provider.issue(
        {
          apiUser: '',
          apiPass: 'secret',
          companyId: 'company-123',
        },
        createDocumentSpec('morning-invalid-creds'),
      ),
    ).rejects.toEqual({
      code: 'CREDENTIAL_INVALID',
      message: 'Morning credentials must include non-empty apiUser, apiPass, and companyId',
    })
    expect(fetchImpl).not.toHaveBeenCalled()
  })
})
