import DataLoader from 'dataloader';
import { format } from 'date-fns';
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 {
  IGetReportByBusinessIdAndDatesQuery,
  IInsertReportParams,
  IInsertReportQuery,
  IUpdateReportParams,
  IUpdateReportQuery,
} from '../types.js';

const getReportByBusinessIdAndDates = sql<IGetReportByBusinessIdAndDatesQuery>`
SELECT *
FROM accounter_schema.pcn874
WHERE business_id = $businessId AND month_date IN $$monthDates;`;

const updateReport = sql<IUpdateReportQuery>`
  UPDATE accounter_schema.pcn874
  SET content = $content
  WHERE business_id = $businessId AND month_date = $monthDate
  RETURNING *;`;

const insertReport = sql<IInsertReportQuery>`
  INSERT INTO accounter_schema.pcn874 (business_id, month_date, content, owner_id)
  VALUES ($businessId, $monthDate, $content, $ownerId)
  RETURNING *;`;

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

  private async batchReportsByBusinessIdAndMonthDatesLoader(
    businessAndDates: readonly [string, string][],
  ) {
    const businessIdsMap = new Map<string, Set<string>>();
    businessAndDates.map(([businessId, monthDate]) => {
      if (businessIdsMap.has(businessId)) {
        businessIdsMap.get(businessId)?.add(monthDate);
      } else {
        businessIdsMap.set(businessId, new Set<string>([monthDate]));
      }
    });
    const reports = (
      await Promise.all(
        Array.from(businessIdsMap.entries()).map(async ([businessId, monthDates]) =>
          getReportByBusinessIdAndDates.run(
            { businessId, monthDates: Array.from(monthDates) },
            this.db,
          ),
        ),
      )
    ).flat();
    return businessAndDates.map(
      ([businessId, monthDate]) =>
        reports.find(
          report =>
            report.business_id === businessId &&
            format(report.month_date, 'yyyy-MM-dd') === monthDate,
        )?.content,
    );
  }

  public getReportByBusinessIdAndMonthDateLoader = new DataLoader(
    (businessAndDates: readonly [string, string][]) =>
      this.batchReportsByBusinessIdAndMonthDatesLoader(businessAndDates),
    { cacheKeyFn: ([businessId, monthDate]) => `${businessId}-${monthDate}` },
  );

  public async updateReport(params: IUpdateReportParams) {
    if (params.businessId && params.monthDate) {
      await this.invalidateByBusinessIdAndMonth(
        params.businessId,
        format(params.monthDate, 'yyyy-MM-dd'),
      );
    }
    return updateReport.run(params, this.db);
  }

  public async insertReport(params: IInsertReportParams) {
    if (params.businessId && params.monthDate) {
      await this.invalidateByBusinessIdAndMonth(
        params.businessId,
        format(params.monthDate, 'yyyy-MM-dd'),
      );
    }
    const { ownerId } = await this.adminContextProvider.getVerifiedAdminContext();
    return insertReport.run(reassureOwnerIdExists(params, ownerId), this.db);
  }

  public async invalidateByBusinessIdAndMonth(businessId: string, monthDate: string) {
    this.getReportByBusinessIdAndMonthDateLoader.clear([businessId, monthDate]);
  }

  public clearCache() {
    this.getReportByBusinessIdAndMonthDateLoader.clearAll();
  }
}
