/**
 * IL Invoice Provider Adapters — P056 invoices-adapters.
 *
 * Five Israeli invoice platform adapters:
 *   morning   — Morning (formerly Chekit) — REST API v2
 *   icount    — iCount — REST API
 *   rivhit    — Rivhit (ריוחית) — REST API
 *   invoice4u — Invoice4U — REST API
 *   easycount — EasyCount — REST API
 *
 * Each adapter implements:
 *   testConnection() → void  (throws on auth failure)
 *   pushInvoice(invoiceId, tenantId, db) → externalId: string
 *
 * Credentials are pre-decrypted by the route before calling these factories.
 * All HTTP calls use fetch() with a 15s timeout.
 */
import { getInvoiceForAdapterPush, type AdapterConfig, type Db, type InvoicePushPayload } from '@zync/db/queries'

// ── Shared types ──────────────────────────────────────────────────────────────

export interface InvoiceAdapter {
  testConnection(): Promise<void>
  pushInvoice(invoiceId: string, tenantId: string, db: Db): Promise<string>
  recordPayment?(
    externalId: string,
    payment: {
      amount: number
      paidAt: Date
      currency: string
      reference: string
    },
    credentials: AdapterConfig,
  ): Promise<void>
}

// ── Fetch helper with timeout ─────────────────────────────────────────────────

async function fetchWithTimeout(
  url: string,
  init: RequestInit,
  timeoutMs = 15_000,
): Promise<Response> {
  const controller = new AbortController()
  const timer = setTimeout(() => controller.abort(), timeoutMs)
  try {
    const res = await fetch(url, { ...init, signal: controller.signal })
    return res
  } finally {
    clearTimeout(timer)
  }
}

class AdapterHttpError extends Error {
  httpStatus: number
  constructor(status: number, message: string) {
    super(message)
    this.name = 'AdapterHttpError'
    this.httpStatus = status
  }
}

// ── Invoice data fetcher ──────────────────────────────────────────────────────

async function fetchInvoiceForPush(
  invoiceId: string,
  tenantId: string,
  db: Db,
): Promise<InvoicePushPayload> {
  const invoice = await getInvoiceForAdapterPush(db, tenantId, invoiceId)

  if (!invoice) throw new Error(`Invoice ${invoiceId} not found`)
  if (!['TAX_ISSUED', 'PAID'].includes(invoice.status)) {
    const err = new Error(`Invoice ${invoiceId} is not in pushable state (${invoice.status})`)
    ;(err as AdapterHttpError).httpStatus = 422
    throw err
  }

  return invoice
}

// ── Morning adapter ────────────────────────────────────────────────────────────
// Docs: https://morning.co.il/api/v2/

export function createMorningAdapter(config: AdapterConfig): InvoiceAdapter {
  const BASE_URL = 'https://api.morning.co.il/v2'
  const headers = () => ({
    'Authorization': `Bearer ${config.apiKey}`,
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  })

  return {
    async testConnection() {
      const res = await fetchWithTimeout(`${BASE_URL}/account`, { method: 'GET', headers: headers() })
      if (!res.ok) {
        throw new AdapterHttpError(res.status, `Morning auth failed: HTTP ${res.status}`)
      }
    },

    async pushInvoice(invoiceId, tenantId, db) {
      const inv = await fetchInvoiceForPush(invoiceId, tenantId, db)
      const payload = {
        doctype: 320, // חשבונית מס receipt
        lang: 'he',
        currency: inv.currency,
        vatType: 1, // included
        income: [
          {
            description: `Invoice ${inv.invoiceNumber ?? inv.proformaNumber ?? invoiceId}`,
            quantity: 1,
            price: parseFloat(inv.total),
            currencyRate: 1,
            vatType: 1,
          },
        ],
        client: {
          name: inv.customerName ?? 'Customer',
          ...(inv.customerEmail ? { emailAddress: inv.customerEmail } : {}),
        },
      }
      const res = await fetchWithTimeout(`${BASE_URL}/doc`, {
        method: 'POST',
        headers: headers(),
        body: JSON.stringify(payload),
      })
      if (!res.ok) {
        const body = await res.text().catch(() => '')
        throw new AdapterHttpError(res.status, `Morning push failed: HTTP ${res.status} — ${body}`)
      }
      const data = await res.json() as { id?: string; docNum?: string }
      return String(data.id ?? data.docNum ?? `morning-${invoiceId}`)
    },
  }
}

// ── iCount adapter ────────────────────────────────────────────────────────────
// Docs: https://icountapi.co.il/

export function createICountAdapter(config: AdapterConfig): InvoiceAdapter {
  const BASE_URL = 'https://api.icount.co.il/api/v3.php'
  const companyId = config.companyId ?? ''
  const apiKey = config.apiKey ?? ''

  function authParams() {
    return { cid: companyId, user: config.metadata?.accountEmail ?? '', pass: apiKey }
  }

  return {
    async testConnection() {
      const params = new URLSearchParams({ ...authParams(), cmd: 'get_client_list', page: '1' })
      const res = await fetchWithTimeout(`${BASE_URL}?${params}`, { method: 'GET' })
      const data = await res.json() as { status?: boolean | number }
      if (!data.status) {
        throw new AdapterHttpError(401, 'iCount auth failed: invalid credentials')
      }
    },

    async pushInvoice(invoiceId, tenantId, db) {
      const inv = await fetchInvoiceForPush(invoiceId, tenantId, db)
      const body = new URLSearchParams({
        ...authParams(),
        cmd: 'create_invrec',
        client_name: inv.customerName ?? 'Customer',
        ...(inv.customerEmail ? { email: inv.customerEmail } : {}),
        total_price: inv.total,
        currency_symbol: inv.currency,
        date: inv.taxIssueDate ?? inv.issueDate ?? new Date().toISOString().slice(0, 10),
        description: `Invoice ${inv.invoiceNumber ?? invoiceId}`,
      })
      const res = await fetchWithTimeout(BASE_URL, {
        method: 'POST',
        body,
      })
      const data = await res.json() as { status?: boolean | number; docid?: string }
      if (!data.status) {
        throw new AdapterHttpError(422, `iCount push failed: ${JSON.stringify(data)}`)
      }
      return String(data.docid ?? `icount-${invoiceId}`)
    },
  }
}

// ── Rivhit adapter ────────────────────────────────────────────────────────────
// Docs: https://api.rivhit.co.il

export function createRivhitAdapter(config: AdapterConfig): InvoiceAdapter {
  const BASE_URL = 'https://api.rivhit.co.il/online/RivhitOnlineAPI.svc'
  const apiKey = config.apiKey ?? ''

  function authHeader() {
    return { 'Authorization': `token ${apiKey}`, 'Content-Type': 'application/json' }
  }

  return {
    async testConnection() {
      const res = await fetchWithTimeout(
        `${BASE_URL}/GetGroupList`,
        { method: 'POST', headers: authHeader(), body: JSON.stringify({}) },
      )
      if (!res.ok) {
        throw new AdapterHttpError(res.status, `Rivhit auth failed: HTTP ${res.status}`)
      }
    },

    async pushInvoice(invoiceId, tenantId, db) {
      const inv = await fetchInvoiceForPush(invoiceId, tenantId, db)
      const payload = {
        PaymentWay: 0,
        DocumentDate: inv.taxIssueDate ?? inv.issueDate,
        PriceTotal: parseFloat(inv.total),
        ClientName: inv.customerName ?? 'Customer',
        ...(inv.customerEmail ? { Email: inv.customerEmail } : {}),
        Remarks: `Zync Invoice ${inv.invoiceNumber ?? invoiceId}`,
        IsPaid: inv.status === 'PAID',
      }
      const res = await fetchWithTimeout(
        `${BASE_URL}/CreateTaxInvoice`,
        { method: 'POST', headers: authHeader(), body: JSON.stringify(payload) },
      )
      if (!res.ok) {
        const body = await res.text().catch(() => '')
        throw new AdapterHttpError(res.status, `Rivhit push failed: HTTP ${res.status} — ${body}`)
      }
      const data = await res.json() as { DocumentID?: string | number }
      return String(data.DocumentID ?? `rivhit-${invoiceId}`)
    },
  }
}

// ── Invoice4U adapter ─────────────────────────────────────────────────────────
// Docs: https://www.invoice4u.co.il/api

export function createInvoice4uAdapter(config: AdapterConfig): InvoiceAdapter {
  const BASE_URL = 'https://api.invoice4u.co.il/Services/ApiService.svc/json'
  const apiKey = config.apiKey ?? ''

  function authHeader() {
    return { 'Content-Type': 'application/json' }
  }

  return {
    async testConnection() {
      const res = await fetchWithTimeout(
        `${BASE_URL}/GetCompanyInfo`,
        {
          method: 'POST',
          headers: authHeader(),
          body: JSON.stringify({ Token: apiKey }),
        },
      )
      const data = await res.json() as { d?: { IsTokenValid?: boolean } }
      if (!data.d?.IsTokenValid) {
        throw new AdapterHttpError(401, 'Invoice4U auth failed: invalid token')
      }
    },

    async pushInvoice(invoiceId, tenantId, db) {
      const inv = await fetchInvoiceForPush(invoiceId, tenantId, db)
      const payload = {
        Token: apiKey,
        Customer: {
          Name: inv.customerName ?? 'Customer',
          ...(inv.customerEmail ? { Email: inv.customerEmail } : {}),
        },
        DocumentRows: [
          {
            Description: `Invoice ${inv.invoiceNumber ?? invoiceId}`,
            Quantity: 1,
            Price: parseFloat(inv.total),
          },
        ],
        DocumentType: 305, // חשבונית מס
        CurrencyId: inv.currency === 'ILS' ? 1 : 0,
      }
      const res = await fetchWithTimeout(
        `${BASE_URL}/AddDocument`,
        { method: 'POST', headers: authHeader(), body: JSON.stringify(payload) },
      )
      const data = await res.json() as { d?: { DocumentID?: string | number } }
      if (!data.d?.DocumentID) {
        throw new AdapterHttpError(422, `Invoice4U push failed: ${JSON.stringify(data)}`)
      }
      return String(data.d.DocumentID)
    },
  }
}

// ── EasyCount adapter ─────────────────────────────────────────────────────────
// Docs: https://easycount.co.il/api

export function createEasycountAdapter(config: AdapterConfig): InvoiceAdapter {
  const BASE_URL = 'https://api.easycount.co.il/api'
  const apiKey = config.apiKey ?? ''
  const companyId = config.companyId ?? ''

  function authHeader() {
    return {
      'x-api-key': apiKey,
      'x-company-id': companyId,
      'Content-Type': 'application/json',
    }
  }

  return {
    async testConnection() {
      const res = await fetchWithTimeout(
        `${BASE_URL}/company`,
        { method: 'GET', headers: authHeader() },
      )
      if (!res.ok) {
        throw new AdapterHttpError(res.status, `EasyCount auth failed: HTTP ${res.status}`)
      }
    },

    async pushInvoice(invoiceId, tenantId, db) {
      const inv = await fetchInvoiceForPush(invoiceId, tenantId, db)
      const payload = {
        docType: 'TAX_INVOICE',
        date: inv.taxIssueDate ?? inv.issueDate ?? new Date().toISOString().slice(0, 10),
        customer: {
          name: inv.customerName ?? 'Customer',
          ...(inv.customerEmail ? { email: inv.customerEmail } : {}),
        },
        items: [
          {
            description: `Invoice ${inv.invoiceNumber ?? invoiceId}`,
            quantity: 1,
            unitPrice: parseFloat(inv.total),
          },
        ],
        currency: inv.currency,
        externalRef: invoiceId,
      }
      const res = await fetchWithTimeout(
        `${BASE_URL}/documents`,
        { method: 'POST', headers: authHeader(), body: JSON.stringify(payload) },
      )
      if (!res.ok) {
        const body = await res.text().catch(() => '')
        throw new AdapterHttpError(res.status, `EasyCount push failed: HTTP ${res.status} — ${body}`)
      }
      const data = await res.json() as { id?: string; docId?: string }
      return String(data.id ?? data.docId ?? `easycount-${invoiceId}`)
    },
  }
}
