import { z } from 'zod';
import {
  ALLOWED_TAX_ADJUSTMENT_CODES,
  AllowedTaxAdjustmentCode,
  NEGATIVE_ALLOWED_TAX_ADJUSTMENTS_CODES,
  TAX_ADJUSTMENT_CODE_NAMES,
  TaxAdjustmentSummaryCode,
} from '../types/index.js';

/**
 * Tax Adjustment Record Schema
 * Based on the SHAAM 6111 specification (IncomeTax_software-houses-6111-2023.pdf)
 */

// Create a set for faster lookup of allowed codes
export const allowedCodesSet = new Set(ALLOWED_TAX_ADJUSTMENT_CODES);
export const negativeAllowedCodesSet = new Set<number>(NEGATIVE_ALLOWED_TAX_ADJUSTMENTS_CODES);

function codeToName(code: AllowedTaxAdjustmentCode): string {
  return `${TAX_ADJUSTMENT_CODE_NAMES[code] ?? 'Unknown code'} (${code})`;
}

// Create the Tax Adjustment record schema
const taxAdjustmentRecordSchema = z
  .object({
    // קוד שדה - Field code
    code: z
      .number()
      .int()
      .refine(code => allowedCodesSet.has(code as AllowedTaxAdjustmentCode), {
        message: 'Invalid code for tax adjustment section',
      }),

    // סכום - Amount
    amount: z.number().int(),
  })
  .superRefine((record, ctx) => {
    // Validate that non-negative codes don't have negative amounts
    if (!negativeAllowedCodesSet.has(record.code) && record.amount < 0) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: `Code ${codeToName(record.code as AllowedTaxAdjustmentCode)} does not allow negative amounts`,
        path: ['amount'],
      });
    }
  });

// Define the validation logic for each summary code
const summaryValidations: Partial<
  Record<
    TaxAdjustmentSummaryCode,
    { formula: (map: Map<number, number>) => number; description: string }
  >
> = {
  //   100:
  //   103:
  //   104: validated on the report level
  370: {
    formula: (map: Map<number, number>) => {
      return (
        [110, 120, 140, 150, 160, 181, 182, 183, 184, 190, 310, 330, 350, 360]
          .map(code => map.get(code) || 0)
          .reduce((a, b) => a + b, 0) -
        [130, 135, 170, 180, 200, 300, 320]
          .map(code => map.get(code) || 0)
          .reduce((a, b) => a + b, 0)
      );
    },
    description:
      '110+120-130-135+140+150+160-170-180+181+182+183+184+190-200-300+310-320+330+350+360',
  },
  //   383:
  //   400: validated on the report level
  500: {
    formula: (map: Map<number, number>) => {
      return (map.get(400) || 0) - (map.get(430) || 0) - (map.get(480) || 0) + (map.get(490) || 0);
    },
    description: '400-430-480+490',
  },
  //   600:
};

// Schema for an array of Tax Adjustment records
export const taxAdjustmentArraySchema = z
  .array(taxAdjustmentRecordSchema)
  .max(150)
  .superRefine((records, ctx) => {
    // Check for unique code values & create a map for faster lookups
    const codeToAmountMap = checkForUniqueCodes(records, ctx);

    // Validate each summary field with its formula
    for (const [codeStr, validation] of Object.entries(summaryValidations)) {
      const code = parseInt(codeStr) as AllowedTaxAdjustmentCode;
      if (codeToAmountMap.has(code)) {
        const actualAmount = codeToAmountMap.get(code);
        const expectedAmount = validation.formula(codeToAmountMap);

        if (actualAmount !== expectedAmount) {
          const index = records.findIndex(r => r.code === code);
          ctx.addIssue({
            code: z.ZodIssueCode.custom,
            message: `Value for code ${codeToName(code)} should equal the sum: ${validation.description}`,
            path: [index, 'amount'],
          });
        }
      }
    }
  });

function checkForUniqueCodes(
  records: {
    code: number;
    amount: number;
  }[],
  ctx: z.RefinementCtx,
) {
  const codeMap = new Map<AllowedTaxAdjustmentCode, number>();
  records.map((record, index) => {
    if (codeMap.has(record.code as AllowedTaxAdjustmentCode)) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: `Duplicate code ${codeToName(record.code as AllowedTaxAdjustmentCode)} found`,
        path: [index, 'code'],
      });
    }
    codeMap.set(record.code as AllowedTaxAdjustmentCode, record.amount);
  });

  return codeMap;
}
