import DataLoader from 'dataloader';
import { Injectable, Scope } from 'graphql-modules';
import { sql } from '@pgtyped/runtime';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { TenantAwareDBClient } from '../../app-providers/tenant-db-client.js';
import type {
  IGetAllSalaryRecordsQuery,
  IGetAllSalaryRecordsResult,
  IGetSalaryRecordsByChargeIdsQuery,
  IGetSalaryRecordsByDatesParams,
  IGetSalaryRecordsByDatesQuery,
  IGetSalaryRecordsByMonthQuery,
  IInsertSalaryRecordsParams,
  IInsertSalaryRecordsQuery,
  IUpdateSalaryRecordParams,
  IUpdateSalaryRecordQuery,
  stringArray,
} from '../types.js';

const getAllSalaryRecords = sql<IGetAllSalaryRecordsQuery>`
    SELECT *
    FROM accounter_schema.salaries;`;

const getSalaryRecordsByMonth = sql<IGetSalaryRecordsByMonthQuery>`
    SELECT *
    FROM accounter_schema.salaries
    WHERE month = $month;`;

const getSalaryRecordsByDates = sql<IGetSalaryRecordsByDatesQuery>`
    SELECT *
    FROM accounter_schema.salaries
    WHERE month >= $fromDate
      AND month <= $toDate;`;

const getSalaryRecordsByChargeIds = sql<IGetSalaryRecordsByChargeIdsQuery>`
  SELECT *
  FROM accounter_schema.salaries
  WHERE $chargeIds && ARRAY[charge_id];
`;

const insertSalaryRecords = sql<IInsertSalaryRecordsQuery>`
  INSERT INTO accounter_schema.salaries (
    owner_id,
    added_vacation_days,
    base_salary,
    bonus,
    charge_id,
    compensations_employer_amount,
    compensations_employer_percentage,
    direct_payment_amount,
    employee,
    employee_id,
    employer,
    gift,
    global_additional_hours,
    health_payment_amount,
    hourly_rate,
    hours,
    job_percentage,
    month,
    pension_employee_amount,
    pension_employee_percentage,
    pension_employer_amount,
    pension_employer_percentage,
    pension_fund_id,
    recovery,
    sickness_days_balance,
    social_security_amount_employee,
    social_security_amount_employer,
    tax_amount,
    training_fund_employee_amount,
    training_fund_employee_percentage,
    training_fund_employer_amount,
    training_fund_employer_percentage,
    training_fund_id,
    travel_and_subsistence,
    vacation_days_balance,
    vacation_takeout,
    work_days,
    zkufot)
  VALUES $$salaryRecords(
    ownerId,
    addedVacationDays,
    baseSalary,
    bonus,
    chargeId,
    compensationsEmployerAmount,
    compensationsEmployerPercentage,
    directPaymentAmount,
    employee,
    employeeId,
    employer,
    gift,
    globalAdditionalHours,
    healthPaymentAmount,
    hourlyRate,
    hours,
    jobPercentage,
    month,
    pensionEmployeeAmount,
    pensionEmployeePercentage,
    pensionEmployerAmount,
    pensionEmployerPercentage,
    pensionFundId,
    recovery,
    sicknessDaysBalance,
    socialSecurityAmountEmployee,
    socialSecurityAmountEmployer,
    taxAmount,
    trainingFundEmployeeAmount,
    trainingFundEmployeePercentage,
    trainingFundEmployerAmount,
    trainingFundEmployerPercentage,
    trainingFundId,
    travelAndSubsistence,
    vacationDaysBalance,
    vacationTakeout,
    workDays,
    zkufot)
  RETURNING *;
`;

const updateSalaryRecord = sql<IUpdateSalaryRecordQuery>`
  UPDATE accounter_schema.salaries
  SET
    added_vacation_days = COALESCE(
      $addedVacationDays,
      added_vacation_days
    ),
    base_salary = COALESCE(
      $baseSalary,
      base_salary
    ),
    bonus = COALESCE(
      $bonus,
      bonus
    ),
    charge_id = COALESCE(
      $chargeId,
      charge_id
    ),
    compensations_employer_amount = COALESCE(
      $compensationsEmployerAmount,
      compensations_employer_amount
    ),
    compensations_employer_percentage = COALESCE(
      $compensationsEmployerPercentage,
      compensations_employer_percentage
    ),
    direct_payment_amount = COALESCE(
      $directPaymentAmount,
      direct_payment_amount
    ),
    employer = COALESCE(
      $employer,
      employer
    ),
    gift = COALESCE(
      $gift,
      gift
    ),
    global_additional_hours = COALESCE(
      $globalAdditionalHours,
      global_additional_hours
    ),
    health_payment_amount = COALESCE(
      $healthPaymentAmount,
      health_payment_amount
    ),
    hourly_rate = COALESCE(
      $hourlyRate,
      hourly_rate
    ),
    hours = COALESCE(
      $hours,
      hours
    ),
    job_percentage = COALESCE(
      $jobPercentage,
      job_percentage
    ),
    pension_employee_amount = COALESCE(
      $pensionEmployeeAmount,
      pension_employee_amount
    ),
    pension_employee_percentage = COALESCE(
      $pensionEmployeePercentage,
      pension_employee_percentage
    ),
    pension_employer_amount = COALESCE(
      $pensionEmployerAmount,
      pension_employer_amount
    ),
    pension_employer_percentage = COALESCE(
      $pensionEmployerPercentage,
      pension_employer_percentage
    ),
    pension_fund_id = COALESCE(
      $pensionFundId,
      pension_fund_id
    ),
    recovery = COALESCE(
      $recovery,
      recovery
    ),
    sickness_days_balance = COALESCE(
      $sicknessDaysBalance,
      sickness_days_balance
    ),
    social_security_amount_employee = COALESCE(
      $socialSecurityAmountEmployee,
      social_security_amount_employee
    ),
    social_security_amount_employer = COALESCE(
      $socialSecurityAmountEmployer,
      social_security_amount_employer
    ),
    tax_amount = COALESCE(
      $taxAmount,
      tax_amount
    ),
    training_fund_employee_amount = COALESCE(
      $trainingFundEmployeeAmount,
      training_fund_employee_amount
    ),
    training_fund_employee_percentage = COALESCE(
      $trainingFundEmployeePercentage,
      training_fund_employee_percentage
    ),
    training_fund_employer_amount = COALESCE(
      $trainingFundEmployerAmount,
      training_fund_employer_amount
    ),
    training_fund_employer_percentage = COALESCE(
      $trainingFundEmployerPercentage,
      training_fund_employer_percentage
    ),
    training_fund_id = COALESCE(
      $trainingFundId,
      training_fund_id
    ),
    travel_and_subsistence = COALESCE(
      $travelAndSubsistence,
      travel_and_subsistence
    ),
    vacation_days_balance = COALESCE(
      $vacationDaysBalance,
      vacation_days_balance
    ),
    vacation_takeout = COALESCE(
      $vacationTakeout,
      vacation_takeout
    ),
    work_days = COALESCE(
      $workDays,
      work_days
    ),
    zkufot = COALESCE(
      $zkufot,
      zkufot
    )
  WHERE month = $month AND employee_id = $employeeId
  RETURNING *;
`;

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

  private async batchSalaryRecordsByMonths(months: readonly string[]) {
    if (months.length === 1) {
      return [await getSalaryRecordsByMonth.run({ month: months[0] }, this.db)];
    }
    const sortedMonths = [...months].sort();
    const salaries = await getSalaryRecordsByDates.run(
      { fromDate: sortedMonths[0], toDate: sortedMonths[sortedMonths.length - 1] },
      this.db,
    );
    return months.map(month => salaries.filter(record => record.month === month));
  }

  public getSalaryRecordsByMonthLoader = new DataLoader((months: readonly string[]) =>
    this.batchSalaryRecordsByMonths(months),
  );

  public getSalaryRecordsByDates(params: IGetSalaryRecordsByDatesParams) {
    return getSalaryRecordsByDates.run(params, this.db);
  }

  private async batchGetSalaryRecordsByChargeIds(chargeIds: stringArray) {
    const salaries = await getSalaryRecordsByChargeIds.run({ chargeIds }, this.db);
    return chargeIds.map(id => salaries.filter(record => record.charge_id === id));
  }

  public getSalaryRecordsByChargeIdLoader = new DataLoader((chargeIds: readonly string[]) =>
    this.batchGetSalaryRecordsByChargeIds(chargeIds as stringArray),
  );

  private allSalariesCache: Promise<IGetAllSalaryRecordsResult[]> | null = null;
  public getAllSalaryRecords() {
    if (this.allSalariesCache) {
      return this.allSalariesCache;
    }
    this.allSalariesCache = getAllSalaryRecords.run(undefined, this.db);
    return this.allSalariesCache;
  }

  public async insertSalaryRecords(params: IInsertSalaryRecordsParams) {
    this.clearCache();
    const { ownerId } = await this.adminContextProvider.getVerifiedAdminContext();
    return insertSalaryRecords.run(
      {
        salaryRecords: params.salaryRecords.map(record => ({
          ...record,
          ownerId: record.ownerId || ownerId,
        })),
      },
      this.db,
    );
  }

  public updateSalaryRecord(params: IUpdateSalaryRecordParams) {
    this.clearCache();
    return updateSalaryRecord.run(params, this.db);
  }

  public clearCache() {
    this.getSalaryRecordsByMonthLoader.clearAll();
    this.getSalaryRecordsByChargeIdLoader.clearAll();
    this.allSalariesCache = null;
  }
}
