import { z } from 'zod';

/**
 * Modulo 10 rekursiv Prüfsummenvalidierung für QR-Referenz
 */
function validateMod10Recursive(reference: string): boolean {
  const table = [0, 9, 4, 6, 8, 2, 7, 1, 3, 5];
  let carry = 0;
  for (let i = 0; i < reference.length - 1; i++) {
    carry = table[(carry + parseInt(reference[i], 10)) % 10];
  }
  return (10 - carry) % 10 === parseInt(reference[reference.length - 1], 10);
}

/**
 * Modulo 97 Prüfsummenvalidierung für Creditor Reference (ISO 11649)
 */
function validateMod97(reference: string): boolean {
  const rearranged = reference.substring(4) + reference.substring(0, 4);
  const numeric = rearranged.split('').map(c => {
    const code = c.charCodeAt(0);
    return code >= 65 ? (code - 55).toString() : c;
  }).join('');
  return BigInt(numeric) % 97n === 1n;
}

/**
 * Prüft ob IID einer QR-IBAN entspricht (30000-31999)
 */
function isQrIban(iban: string): boolean {
  const cleanIban = iban.replace(/\s/g, '').toUpperCase();
  if (!cleanIban.startsWith('CH') && !cleanIban.startsWith('LI')) {
    return false;
  }
  const iid = parseInt(cleanIban.substring(4, 9), 10);
  return iid >= 30000 && iid <= 31999;
}

// IBAN Schema (Schweiz/Liechtenstein)
const ibanSchema = z.string()
  .transform(val => val.replace(/\s/g, '').toUpperCase())
  .refine(
    val => /^(CH|LI)\d{2}[A-Z0-9]{5}\d{12}$/.test(val),
    'Ungültiges IBAN-Format. Muss CH oder LI IBAN sein.'
  );

// Strukturierte Adresse
const structuredAddressSchema = z.object({
  name: z.string().min(1, 'Name ist erforderlich').max(70, 'Name darf max. 70 Zeichen haben'),
  street: z.string().max(70, 'Strasse darf max. 70 Zeichen haben').optional(),
  buildingNumber: z.string().max(16, 'Hausnummer darf max. 16 Zeichen haben').optional(),
  postalCode: z.string().min(1, 'PLZ ist erforderlich').max(16, 'PLZ darf max. 16 Zeichen haben'),
  city: z.string().min(1, 'Ort ist erforderlich').max(35, 'Ort darf max. 35 Zeichen haben'),
  country: z.string().length(2, 'Land muss ISO 3166-1 alpha-2 sein (z.B. CH)').regex(/^[A-Z]{2}$/, 'Land muss Grossbuchstaben sein'),
});

// Referenz Schema
const referenceSchema = z.object({
  type: z.enum(['QRR', 'SCOR', 'NON']),
  value: z.string().optional(),
}).refine(
  (ref) => {
    if (ref.type === 'QRR') {
      if (!ref.value || !/^\d{27}$/.test(ref.value)) {
        return false;
      }
      return validateMod10Recursive(ref.value);
    }
    if (ref.type === 'SCOR') {
      if (!ref.value || !/^RF\d{2}[A-Z0-9]{1,21}$/.test(ref.value)) {
        return false;
      }
      return validateMod97(ref.value);
    }
    if (ref.type === 'NON') {
      return !ref.value || ref.value === '';
    }
    return false;
  },
  {
    message: 'Referenz-Wert ungültig oder stimmt nicht mit Referenz-Typ überein',
  }
);

// Hauptschema für QR-Invoice Request
export const qrInvoiceRequestSchema = z.object({
  creditor: z.object({
    iban: ibanSchema,
    address: structuredAddressSchema,
  }),
  amount: z.number()
    .min(0.01, 'Betrag muss mindestens 0.01 sein')
    .max(999999999.99, 'Betrag darf max. 999999999.99 sein')
    .refine(val => Number.isFinite(val) && Math.round(val * 100) === val * 100, 'Betrag darf max. 2 Dezimalstellen haben')
    .optional(),
  currency: z.enum(['CHF', 'EUR'], {
    errorMap: () => ({ message: 'Währung muss CHF oder EUR sein' }),
  }),
  reference: referenceSchema.optional().default({ type: 'NON' }),
  debtor: z.object({
    address: structuredAddressSchema,
  }).optional(),
  additionalInformation: z.object({
    message: z.string().max(140, 'Mitteilung darf max. 140 Zeichen haben').optional(),
    billingInformation: z.string().max(140, 'Rechnungsinformationen dürfen max. 140 Zeichen haben').optional(),
  }).optional(),
  options: z.object({
    language: z.enum(['de', 'fr', 'it', 'en']).default('de'),
    separate: z.boolean().default(true),
  }).optional().default({}),
}).superRefine((data, ctx) => {
  const qrIban = isQrIban(data.creditor.iban);

  // QRR erfordert QR-IBAN
  if (data.reference?.type === 'QRR' && !qrIban) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      message: 'QR-Referenz (QRR) erfordert eine QR-IBAN (IID 30000-31999)',
      path: ['creditor', 'iban'],
    });
  }

  // SCOR/NON erfordert reguläre IBAN
  if ((data.reference?.type === 'SCOR' || data.reference?.type === 'NON') && qrIban) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      message: 'SCOR/NON Referenz erfordert eine reguläre IBAN (nicht QR-IBAN)',
      path: ['creditor', 'iban'],
    });
  }
});

export type QRInvoiceRequestInput = z.input<typeof qrInvoiceRequestSchema>;
export type QRInvoiceRequestOutput = z.output<typeof qrInvoiceRequestSchema>;
