// ==================== GUTSCHRIFTEN (CREDIT NOTES) ====================
// CRUD für Vertrags-Gutschriften (Subventionen: Geld/Sachwert) inkl.
// USt-Berechnung (pro Gutschrift wählbar: vatRelevant + Basis Netto/Brutto).

import fs from 'fs';
import path from 'path';
import prisma from '../lib/prisma.js';
import { ApiError } from '../utils/apiError.js';
import { assignNextNumber } from './creditNoteNumberRange.service.js';
import { CreditNoteType, CreditNoteCustomerType, CreditNoteAmountBasis } from '@prisma/client';

// Löscht eine hochgeladene/erzeugte Datei von der Platte (best effort).
function deleteFileIfExists(filePath: string | null) {
  if (!filePath) return;
  const absolute = path.join(process.cwd(), filePath);
  if (fs.existsSync(absolute)) {
    try {
      fs.unlinkSync(absolute);
    } catch (error) {
      console.error('Fehler beim Löschen der Gutschrift-Datei:', absolute, error);
    }
  }
}

const round2 = (n: number) => Math.round((n + Number.EPSILON) * 100) / 100;

export interface AmountResult {
  amountNet: number;
  amountVat: number;
  amountGross: number;
}

/**
 * Rechnet aus dem eingegebenen Betrag Netto/USt/Brutto aus.
 *  - vatRelevant = false → keine USt: net = brutto = Betrag, USt = 0.
 *  - vatRelevant = true, Basis NETTO  → USt aufschlagen.
 *  - vatRelevant = true, Basis BRUTTO → USt herausrechnen.
 */
export function computeAmounts(params: {
  amount: number;
  vatRelevant: boolean;
  amountBasis: CreditNoteAmountBasis;
  vatRate: number;
}): AmountResult {
  const amount = round2(params.amount);
  if (!params.vatRelevant || params.vatRate <= 0) {
    return { amountNet: amount, amountVat: 0, amountGross: amount };
  }
  const rate = params.vatRate / 100;
  if (params.amountBasis === 'NETTO') {
    const net = amount;
    const vat = round2(net * rate);
    return { amountNet: net, amountVat: vat, amountGross: round2(net + vat) };
  }
  // BRUTTO
  const gross = amount;
  const net = round2(gross / (1 + rate));
  return { amountNet: net, amountVat: round2(gross - net), amountGross: gross };
}

const ALLOWED_TYPES = new Set(['GELD', 'SACHWERT']);
const ALLOWED_CUSTOMER_TYPES = new Set(['PRIVAT', 'FIRMA']);
const ALLOWED_BASIS = new Set(['NETTO', 'BRUTTO']);

export interface CreateCreditNoteInput {
  type: string;
  sachwertDescription?: string | null;
  customerType?: string;
  vatRelevant?: boolean;
  amountBasis?: string;
  vatRate?: number;
  amount: number; // eingegebener Betrag (Basis siehe amountBasis)
  currency?: string;
  creditDate: string;
  place?: string | null;
  signedAt?: string | null;
  goodsReceived?: boolean;
  payoutBankCardId?: number | null; // nur GELD: Auszahlungskonto des Kunden
  notes?: string | null;
}

function validateAndNormalize(input: CreateCreditNoteInput) {
  if (!ALLOWED_TYPES.has(input.type)) {
    throw new ApiError(400, 'Ungültige Gutschrift-Art');
  }
  const type = input.type as CreditNoteType;

  if (type === 'SACHWERT' && (!input.sachwertDescription || !input.sachwertDescription.trim())) {
    throw new ApiError(400, 'Bei Sachwerten bitte beschreiben, was gewährt wird.');
  }

  const customerType = (input.customerType && ALLOWED_CUSTOMER_TYPES.has(input.customerType)
    ? input.customerType
    : 'PRIVAT') as CreditNoteCustomerType;

  const amountBasis = (input.amountBasis && ALLOWED_BASIS.has(input.amountBasis)
    ? input.amountBasis
    : 'BRUTTO') as CreditNoteAmountBasis;

  const amount = Number(input.amount);
  if (!Number.isFinite(amount) || amount < 0) {
    throw new ApiError(400, 'Ungültiger Betrag');
  }

  const vatRelevant = !!input.vatRelevant;
  const vatRate = Number.isFinite(Number(input.vatRate)) ? Number(input.vatRate) : 19;
  if (vatRate < 0 || vatRate > 100) {
    throw new ApiError(400, 'Ungültiger USt-Satz');
  }

  const creditDate = new Date(input.creditDate);
  if (isNaN(creditDate.getTime())) {
    throw new ApiError(400, 'Ungültiges Datum');
  }

  const signedAt = input.signedAt ? new Date(input.signedAt) : null;
  if (signedAt && isNaN(signedAt.getTime())) {
    throw new ApiError(400, 'Ungültiges Unterschriftsdatum');
  }

  const amounts = computeAmounts({ amount, vatRelevant, amountBasis, vatRate });

  // Auszahlungskonto nur bei GELD relevant; bei Sachwert immer leeren.
  let payoutBankCardId: number | null = null;
  if (type === 'GELD' && input.payoutBankCardId != null && input.payoutBankCardId !== ('' as unknown)) {
    const parsed = Number(input.payoutBankCardId);
    if (!Number.isInteger(parsed) || parsed < 1) {
      throw new ApiError(400, 'Ungültiges Auszahlungskonto');
    }
    payoutBankCardId = parsed;
  }

  return {
    type,
    sachwertDescription: type === 'SACHWERT' ? input.sachwertDescription!.trim() : null,
    customerType,
    vatRelevant,
    amountBasis,
    vatRate,
    ...amounts,
    currency: (input.currency || 'EUR').slice(0, 3).toUpperCase(),
    creditDate,
    place: input.place?.trim() || null,
    signedAt,
    goodsReceived: !!input.goodsReceived,
    payoutBankCardId,
    notes: input.notes?.trim() || null,
  };
}

// Stellt sicher, dass die gewählte Bankkarte dem Kunden des Vertrags gehört
// (kein Fremdkonto unterschieben).
async function assertBankCardBelongsToContract(contractId: number, bankCardId: number) {
  const [contract, card] = await Promise.all([
    prisma.contract.findUnique({ where: { id: contractId }, select: { customerId: true } }),
    prisma.bankCard.findUnique({ where: { id: bankCardId }, select: { customerId: true } }),
  ]);
  if (!card || !contract || card.customerId !== contract.customerId) {
    throw new ApiError(400, 'Das gewählte Auszahlungskonto gehört nicht zum Kunden dieses Vertrags.');
  }
}

export async function getCreditNotesByContract(contractId: number) {
  return prisma.creditNote.findMany({
    where: { contractId },
    orderBy: { createdAt: 'desc' },
  });
}

export async function getCreditNoteById(id: number) {
  return prisma.creditNote.findUnique({ where: { id } });
}

// Ermittelt die Formular-Vorbelegung aus dem Kunden:
//  - customerType: Firma vs. Privat (aus Kunde.type)
//  - vatRelevant : Default nur bei Firmenkunde OHNE USt-Befreiung
//    (Kleinunternehmer §19 → wie Privat, keine USt-Vorbelegung).
// WICHTIG: Das ist nur die Vorbelegung. Jede angelegte Gutschrift speichert
// ihren eigenen Snapshot; ein späterer Statuswechsel des Kunden ändert
// bestehende Gutschriften nicht.
export async function getCreditNoteDefaults(contractId: number) {
  const contract = await prisma.contract.findUnique({
    where: { id: contractId },
    select: {
      bankCardId: true,
      customer: {
        select: {
          type: true,
          vatExempt: true,
          bankCards: {
            where: { isActive: true },
            select: { id: true, iban: true, accountHolder: true, bankName: true, description: true },
            orderBy: { createdAt: 'asc' },
          },
        },
      },
    },
  });
  const isBusiness = contract?.customer?.type === 'BUSINESS';
  const vatExempt = !!contract?.customer?.vatExempt;
  return {
    customerType: (isBusiness ? 'FIRMA' : 'PRIVAT') as CreditNoteCustomerType,
    vatRelevant: isBusiness && !vatExempt,
    // Bankkarten des Kunden für das Auszahlungskonto-Dropdown; die
    // Vertrags-Abbuchkarte als Default-Vorschlag markiert.
    bankCards: contract?.customer?.bankCards ?? [],
    contractBankCardId: contract?.bankCardId ?? null,
  };
}

export async function createCreditNote(
  contractId: number,
  input: CreateCreditNoteInput,
  createdBy?: string,
) {
  const contract = await prisma.contract.findUnique({ where: { id: contractId }, select: { id: true } });
  if (!contract) {
    throw new ApiError(404, 'Vertrag nicht gefunden');
  }

  const normalized = validateAndNormalize(input);
  if (normalized.payoutBankCardId) {
    await assertBankCardBelongsToContract(contractId, normalized.payoutBankCardId);
  }
  const number = await assignNextNumber();

  return prisma.creditNote.create({
    data: {
      contractId,
      number,
      ...normalized,
      createdBy,
    },
  });
}

export async function updateCreditNote(id: number, input: CreateCreditNoteInput) {
  const existing = await prisma.creditNote.findUnique({ where: { id } });
  if (!existing) {
    throw new ApiError(404, 'Gutschrift nicht gefunden');
  }
  const normalized = validateAndNormalize(input);
  if (normalized.payoutBankCardId) {
    await assertBankCardBelongsToContract(existing.contractId, normalized.payoutBankCardId);
  }
  // Nummer bleibt unverändert (einmal vergeben = fix). Ein evtl. schon
  // erzeugtes PDF ist nach inhaltlicher Änderung veraltet → Pfad leeren
  // (der User erzeugt es bei Bedarf neu) und die alte Datei entfernen,
  // damit sie nicht verwaist liegen bleibt.
  if (existing.pdfPath) deleteFileIfExists(existing.pdfPath);
  return prisma.creditNote.update({ where: { id }, data: { ...normalized, pdfPath: null } });
}

export async function deleteCreditNote(id: number) {
  const existing = await prisma.creditNote.findUnique({ where: { id } });
  if (!existing) {
    throw new ApiError(404, 'Gutschrift nicht gefunden');
  }
  // Verwaiste Dateien vermeiden: generiertes PDF + Überweisungsbeleg von der
  // Platte entfernen (Pentest R138-Hinweis).
  deleteFileIfExists(existing.pdfPath);
  deleteFileIfExists(existing.receiptPath);
  return prisma.creditNote.delete({ where: { id } });
}

// Setzt/aktualisiert den Pfad des hochgeladenen Überweisungsbelegs.
export async function setReceiptPath(id: number, receiptPath: string | null) {
  return prisma.creditNote.update({ where: { id }, data: { receiptPath } });
}
