import DataLoader from 'dataloader';
import { Injectable, Scope } from 'graphql-modules';
import { sql } from '@pgtyped/runtime';
import { reassureOwnerIdExists } from '../../../shared/helpers/index.js';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { TenantAwareDBClient } from '../../app-providers/tenant-db-client.js';
import type {
  IGetChargeIdsByPaymentIdsQuery,
  IGetInvoicesByIdsQuery,
  IGetInvoicesByIssueDatesQuery,
  IGetReceiptToChargeQuery,
  IInsertDeelInvoiceRecordsParams,
  IInsertDeelInvoiceRecordsQuery,
} from '../types.js';

const getInvoicesByIssueDates = sql<IGetInvoicesByIssueDatesQuery>`
  SELECT *
  FROM accounter_schema.deel_invoices
  WHERE issued_at >= $from
    AND issued_at <= $to;`;

const getInvoicesByIds = sql<IGetInvoicesByIdsQuery>`
  SELECT *
  FROM accounter_schema.deel_invoices
  WHERE id in $$ids;`;

const getChargeIdsByPaymentIds = sql<IGetChargeIdsByPaymentIdsQuery>`
  SELECT d.charge_id, i.payment_id
  FROM accounter_schema.deel_invoices i
  LEFT JOIN accounter_schema.documents d
    ON i.document_id = d.id AND d.charge_id IS NOT NULL
  WHERE i.payment_id in $$paymentIds;`;

const getReceiptToCharge = sql<IGetReceiptToChargeQuery>`
SELECT DISTINCT ON (di.payment_id)
    di.payment_id,
    d.charge_id
FROM
    accounter_schema.deel_invoices di
LEFT JOIN accounter_schema.documents d
    ON d.id = di.document_id
ORDER BY
    di.payment_id, di.created_at ASC;`;

const insertDeelInvoiceRecords = sql<IInsertDeelInvoiceRecordsQuery>`
      INSERT INTO accounter_schema.deel_invoices (
        id,
        document_id,
        amount,
        contract_id,
        created_at,
        currency,
        deel_fee,
        due_date,
        is_overdue,
        issued_at,
        label,
        paid_at,
        status,
        total,
        vat_id,
        vat_percentage,
        vat_total,
        adjustment,
        approve_date,
        approvers,
        bonus,
        commissions,
        contract_country,
        contract_start_date,
        contract_type,
        contractor_email,
        contractor_employee_name,
        contractor_unique_identifier,
        deductions,
        expenses,
        frequency,
        general_ledger_account,
        group_id,
        others,
        overtime,
        payment_currency,
        pro_rata,
        processing_fee,
        "work",
        total_payment_currency,
        payment_id,
        recipient_legal_entity_id,
        owner_id)
      VALUES ($id,
        $documentId,
        $amount,
        $contractId,
        $createdAt,
        $currency,
        $deelFee,
        $dueDate,
        $isOverdue,
        $issuedAt,
        $label,
        $paidAt,
        $status,
        $total,
        $vatId,
        $vatPercentage,
        $vatTotal,
        $adjustment,
        $approveDate,
        $approvers,
        $bonus,
        $commissions,
        $contractCountry,
        $contractStartDate,
        $contractType,
        $contractorEmail,
        $contractorEmployeeName,
        $contractorUniqueIdentifier,
        $deductions,
        $expenses,
        $frequency,
        $generalLedgerAccount,
        $groupId,
        $others,
        $overtime,
        $paymentCurrency,
        $proRata,
        $processingFee,
        $work,
        $totalPaymentCurrency,
        $paymentId,
        $recipientLegalEntityId,
        $ownerId)
      RETURNING *;`;

@Injectable({
  scope: Scope.Operation,
  global: true,
})
export class DeelInvoicesProvider {
  constructor(
    private db: TenantAwareDBClient,
    private adminContextProvider: AdminContextProvider,
  ) {}

  private async batchInvoicesByIssueDates(issueDates: readonly Date[]) {
    const times = issueDates.map(date => date.getTime());
    const from = new Date(Math.min(...times));
    const to = new Date(Math.max(...times));
    const records = await getInvoicesByIssueDates.run({ from, to }, this.db);
    return issueDates.map(date => {
      return records.filter(record => record.issued_at.getTime() === date.getTime());
    });
  }

  public async getInvoicesByIssueDates(from: Date, to: Date) {
    try {
      return getInvoicesByIssueDates.run({ from, to }, this.db);
    } catch (e) {
      const message = `Error getting Deel invoices by issue dates`;
      console.error(message, e);
      throw new Error(message, { cause: e });
    }
  }

  public getInvoicesByIssueDateLoader = new DataLoader(
    (dates: readonly Date[]) => this.batchInvoicesByIssueDates(dates),
    { cacheKeyFn: date => date.getTime().toString() },
  );

  private async batchInvoicesByIds(ids: readonly string[]) {
    const invoices = await getInvoicesByIds.run({ ids }, this.db);
    return ids.map(id => invoices.find(invoice => invoice.id === id));
  }

  public getInvoicesByIdLoader = new DataLoader((ids: readonly string[]) =>
    this.batchInvoicesByIds(ids),
  );

  private async batchChargeIdsByPaymentIds(paymentIds: readonly string[]) {
    const matches = await getChargeIdsByPaymentIds.run({ paymentIds }, this.db);
    return paymentIds.map(
      paymentId => matches.find(match => match.payment_id === paymentId)?.charge_id,
    );
  }

  public getChargeIdByPaymentIdLoader = new DataLoader((paymentIds: readonly string[]) =>
    this.batchChargeIdsByPaymentIds(paymentIds),
  );

  public async getReceiptToCharge() {
    try {
      const records = await getReceiptToCharge.run(undefined, this.db);
      const receiptChargeMap = new Map<string, string>();
      for (const record of records) {
        if (record.charge_id) {
          receiptChargeMap.set(record.payment_id, record.charge_id);
        }
      }
      return receiptChargeMap;
    } catch (e) {
      const message = `Error getting receipt to charge mapping`;
      console.error(message, e);
      throw new Error(message, { cause: e });
    }
  }

  public async insertDeelInvoiceRecords(params: IInsertDeelInvoiceRecordsParams) {
    try {
      const { ownerId } = await this.adminContextProvider.getVerifiedAdminContext();
      // invalidate cache
      return insertDeelInvoiceRecords.run(reassureOwnerIdExists(params, ownerId), this.db);
    } catch (e) {
      const message = `Error inserting Deel invoice`;
      console.error(message, e);
      throw new Error(message, { cause: e });
    }
  }
}
