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 { validateClientIntegrations } from '../helpers/clients.helper.js';
import type {
  IDeleteClientQuery,
  IGetAllClientsQuery,
  IGetAllClientsResult,
  IGetClientsByGreenInvoiceIdsQuery,
  IGetClientsByIdsQuery,
  IInsertClientParams,
  IInsertClientQuery,
  IUpdateClientParams,
  IUpdateClientQuery,
} from '../types.js';

const getAllClients = sql<IGetAllClientsQuery>`
  SELECT *
  FROM accounter_schema.clients;
`;

const getClientsByIds = sql<IGetClientsByIdsQuery>`
  SELECT *
  FROM accounter_schema.clients
  WHERE business_id IN $$businessIds;
`;

const getClientsByGreenInvoiceIds = sql<IGetClientsByGreenInvoiceIdsQuery>`
  SELECT *, (integrations->>'greenInvoiceId')::uuid as green_invoice_business_id
  FROM accounter_schema.clients
  WHERE (integrations->>'greenInvoiceId')::uuid in $$greenInvoiceBusinessIds;
`;

const updateClient = sql<IUpdateClientQuery>`
  UPDATE accounter_schema.clients
  SET
  emails = COALESCE(
    $emails,
    emails,
    NULL
  ),
  business_id = COALESCE(
    $newBusinessId,
    business_id,
    NULL
  ),
  integrations = COALESCE(
    $integrations,
    integrations,
    NULL
  )
  WHERE
    business_id = $businessId
  RETURNING *;
`;

const deleteClient = sql<IDeleteClientQuery>`
  DELETE FROM accounter_schema.clients
  WHERE business_id = $businessId
  RETURNING business_id;
`;

const insertClient = sql<IInsertClientQuery>`
    INSERT INTO accounter_schema.clients (business_id, emails, integrations, owner_id)
    VALUES ($businessId, $emails, $integrations, $ownerId)
    RETURNING *;`;

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

  private allClientsPython: Promise<IGetAllClientsResult[]> | null = null;
  public async getAllClients() {
    if (this.allClientsPython) {
      return this.allClientsPython;
    }
    this.allClientsPython = getAllClients.run(undefined, this.db).then(clients => {
      clients.map(client => {
        this.getClientByIdLoader.prime(client.business_id, client);
        try {
          const { greenInvoiceId } = validateClientIntegrations(client.integrations ?? {});
          if (greenInvoiceId) {
            this.getClientByGreenInvoiceIdLoader.prime(greenInvoiceId, {
              ...client,
              green_invoice_business_id: greenInvoiceId,
            });
          }
        } catch {
          // swallow errors
        }
      });
      return clients;
    });
    return this.allClientsPython;
  }

  private async batchClientsByIds(ids: readonly string[]) {
    try {
      const matches = await getClientsByIds.run({ businessIds: ids }, this.db);

      return ids.map(id => matches.find(match => match.business_id === id));
    } catch (e) {
      console.error(e);
      return ids.map(() => undefined);
    }
  }

  public getClientByIdLoader = new DataLoader((keys: readonly string[]) =>
    this.batchClientsByIds(keys),
  );

  private async batchClientsByGreenInvoiceIds(greenInvoiceIds: readonly string[]) {
    try {
      const matches = await getClientsByGreenInvoiceIds.run(
        { greenInvoiceBusinessIds: greenInvoiceIds },
        this.db,
      );

      matches.map(match => {
        this.getClientByIdLoader.prime(match.business_id, match);
      });

      return greenInvoiceIds.map(id =>
        matches.find(match => match.green_invoice_business_id === id),
      );
    } catch (e) {
      console.error(e);
      return greenInvoiceIds.map(() => undefined);
    }
  }

  public getClientByGreenInvoiceIdLoader = new DataLoader((keys: readonly string[]) =>
    this.batchClientsByGreenInvoiceIds(keys),
  );

  public async updateClient(params: IUpdateClientParams) {
    if (params.businessId) {
      this.getClientByIdLoader.clear(params.businessId);
    }
    this.clearCache();
    return updateClient.run(params, this.db);
  }

  public async deleteClient(businessId: string) {
    this.clearCache();
    return deleteClient.run({ businessId }, this.db);
  }

  public async insertClient(params: IInsertClientParams) {
    this.clearCache();
    const { ownerId } = await this.adminContextProvider.getVerifiedAdminContext();
    return insertClient.run(reassureOwnerIdExists(params, ownerId), this.db);
  }

  public clearCache() {
    this.allClientsPython = null;
    this.getClientByIdLoader.clearAll();
    this.getClientByGreenInvoiceIdLoader.clearAll();
  }
}
