<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Service;

use DateTime;
use OCA\Mitgliederverwaltung\Db\MemberMapper;
use OCA\Mitgliederverwaltung\Db\AddressMapper;
use OCA\Mitgliederverwaltung\Db\FamilyMapper;
use OCA\Mitgliederverwaltung\Db\PhoneMapper;
use OCA\Mitgliederverwaltung\Db\EmailMapper;
use OCA\Mitgliederverwaltung\Db\FeeRecordMapper;
use OCA\Mitgliederverwaltung\Db\Member;
use OCP\DB\Exception;
use Psr\Log\LoggerInterface;

/**
 * CSV export service with UTF-8 BOM for Excel compatibility.
 *
 * Generates semicolon-delimited CSV files with German column headers.
 * All fields are properly quoted and escaped.
 *
 * Part of Issue #47.
 */
class CsvExportService {

    private MemberMapper $memberMapper;
    private AddressMapper $addressMapper;
    private FamilyMapper $familyMapper;
    private PhoneMapper $phoneMapper;
    private EmailMapper $emailMapper;
    private FeeRecordMapper $feeRecordMapper;
    private EncryptionService $encryptionService;
    private LoggerInterface $logger;

    /** UTF-8 BOM for Excel compatibility */
    private const UTF8_BOM = "\xEF\xBB\xBF";

    /** CSV delimiter — semicolon for German locale */
    private const DELIMITER = ';';

    public function __construct(
        MemberMapper $memberMapper,
        AddressMapper $addressMapper,
        FamilyMapper $familyMapper,
        PhoneMapper $phoneMapper,
        EmailMapper $emailMapper,
        FeeRecordMapper $feeRecordMapper,
        EncryptionService $encryptionService,
        LoggerInterface $logger
    ) {
        $this->memberMapper = $memberMapper;
        $this->addressMapper = $addressMapper;
        $this->familyMapper = $familyMapper;
        $this->phoneMapper = $phoneMapper;
        $this->emailMapper = $emailMapper;
        $this->feeRecordMapper = $feeRecordMapper;
        $this->encryptionService = $encryptionService;
        $this->logger = $logger;
    }

    // ── Member list export ──────────────────────────────────────────

    /**
     * Export a full member list as CSV.
     *
     * @param string|null $statusFilter Optional status filter (aktiv, inaktiv)
     * @return array{content: string, filename: string}
     * @throws Exception
     */
    public function exportMemberList(?string $statusFilter = null): array {
        if ($statusFilter !== null && $statusFilter !== '') {
            $members = $this->memberMapper->findByStatus($statusFilter);
        } else {
            $members = $this->memberMapper->findAll();
        }

        $headers = [
            'Nachname',
            'Vorname',
            'Geburtsdatum',
            'Geschlecht',
            'Rolle',
            'Status',
            'Eintritt',
            'Austritt',
            'Adresse',
            'PLZ',
            'Ort',
            'Telefon',
            'E-Mail',
            'Notizen',
        ];

        $rows = [];
        foreach ($members as $member) {
            $id = $member->getId();
            $addresses = $this->addressMapper->findByMemberId($id);
            $phones = $this->phoneMapper->findByMemberId($id);
            $emails = $this->emailMapper->findByMemberId($id);

            // Primary address (or first)
            $primaryAddr = null;
            foreach ($addresses as $addr) {
                if ($addr->getIsPrimary() || $primaryAddr === null) {
                    $primaryAddr = $addr;
                }
            }

            $rows[] = [
                $member->getNachname(),
                $member->getVorname(),
                $member->getGeburtsdatum() ?? '',
                $this->formatGeschlecht($member->getGeschlecht()),
                $this->formatRolle($member->getRolle()),
                $this->formatStatus($member->getStatus()),
                $member->getEintritt(),
                $member->getAustritt() ?? '',
                $primaryAddr ? $primaryAddr->getStrasse() : '',
                $primaryAddr ? $primaryAddr->getPlz() : '',
                $primaryAddr ? $primaryAddr->getOrt() : '',
                count($phones) > 0 ? $phones[0]->getNumberE164() : '',
                count($emails) > 0 ? $emails[0]->getEmail() : '',
                $member->getNotizen() ?? '',
            ];
        }

        $date = (new DateTime())->format('Y-m-d');
        $filterSuffix = $statusFilter ? '_' . $statusFilter : '';
        $filename = "Mitgliederliste{$filterSuffix}_{$date}.csv";

        return [
            'content' => $this->buildCsv($headers, $rows),
            'filename' => $filename,
        ];
    }

    // ── Fee records export ──────────────────────────────────────────

    /**
     * Export fee records for a year as CSV.
     *
     * @return array{content: string, filename: string}
     * @throws Exception
     */
    public function exportFeeRecords(int $year): array {
        $records = $this->feeRecordMapper->findByYear($year);

        $headers = [
            'Mitglied-ID',
            'Nachname',
            'Vorname',
            'Jahr',
            'Betrag',
            'Bezahlt',
            'Zahlungsdatum',
            'Manuell angepasst',
            'Notizen',
        ];

        $rows = [];
        foreach ($records as $record) {
            // Get member info
            $memberName = 'Unbekannt';
            $memberVorname = '';
            try {
                $member = $this->memberMapper->findById($record->getMemberId());
                $memberName = $member->getNachname();
                $memberVorname = $member->getVorname();
            } catch (\Exception $e) {
                // Member may have been deleted
            }

            $rows[] = [
                (string)$record->getMemberId(),
                $memberName,
                $memberVorname,
                (string)$record->getYear(),
                $record->getAmount() ?? '0.00',
                $record->getPaid() ? 'Ja' : 'Nein',
                $record->getPaymentDate() ?? '',
                $record->getManuellAngepasst() ? 'Ja' : 'Nein',
                $record->getNotes() ?? '',
            ];
        }

        $filename = "Beitraege_{$year}.csv";

        return [
            'content' => $this->buildCsv($headers, $rows),
            'filename' => $filename,
        ];
    }

    // ── Birthday list export ────────────────────────────────────────

    /**
     * Export members with birthday in a given month.
     *
     * @return array{content: string, filename: string}
     * @throws Exception
     */
    public function exportBirthdayList(int $month): array {
        $members = $this->memberMapper->findByBirthdayMonth($month);

        $headers = [
            'Nachname',
            'Vorname',
            'Geburtsdatum',
            'Alter',
            'Status',
        ];

        $now = new DateTime();
        $rows = [];
        foreach ($members as $member) {
            $birthday = new DateTime($member->getGeburtsdatum());
            $age = $now->diff($birthday)->y;

            $rows[] = [
                $member->getNachname(),
                $member->getVorname(),
                $member->getGeburtsdatum(),
                (string)$age,
                $this->formatStatus($member->getStatus()),
            ];
        }

        $monthNames = [
            1 => 'Januar', 2 => 'Februar', 3 => 'Maerz', 4 => 'April',
            5 => 'Mai', 6 => 'Juni', 7 => 'Juli', 8 => 'August',
            9 => 'September', 10 => 'Oktober', 11 => 'November', 12 => 'Dezember',
        ];
        $monthName = $monthNames[$month] ?? 'Monat';
        $filename = "Geburtstage_{$monthName}.csv";

        return [
            'content' => $this->buildCsv($headers, $rows),
            'filename' => $filename,
        ];
    }

    // ── Banking list export (sensitive) ────────────────────────────

    /**
     * Export banking data for all families with members.
     *
     * This is a SENSITIVE export: IBAN and Kontoinhaber are decrypted
     * for inclusion. The caller must verify banking permission before
     * invoking this method.
     *
     * Part of Issue #48.
     *
     * @return array{content: string, filename: string}
     * @throws Exception
     */
    public function exportBankingList(): array {
        $families = $this->familyMapper->findAll();

        $headers = [
            'Familie',
            'Kontoinhaber',
            'IBAN',
            'BIC',
            'Kreditinstitut',
            'Mitglieder',
        ];

        $rows = [];
        foreach ($families as $family) {
            $kontoinhaber = $this->encryptionService->decrypt($family->getKontoinhaberEncrypted());
            $iban = $this->encryptionService->decrypt($family->getIbanEncrypted());

            // Get family members
            $members = $this->memberMapper->findByFamily($family->getId());
            $memberNames = array_map(
                fn($m) => $m->getVorname() . ' ' . $m->getNachname(),
                $members
            );

            $rows[] = [
                $family->getName(),
                $kontoinhaber ?? '',
                $iban ?? '',
                $family->getBic() ?? '',
                $family->getKreditinstitut() ?? '',
                implode(', ', $memberNames),
            ];
        }

        $date = (new DateTime())->format('Y-m-d');
        $filename = "Bankverbindungen_{$date}.csv";

        return [
            'content' => $this->buildCsv($headers, $rows),
            'filename' => $filename,
        ];
    }

    // ── CSV builder ─────────────────────────────────────────────────

    /**
     * Build a CSV string from headers and row data.
     *
     * @param string[] $headers
     * @param array[] $rows Array of string arrays
     * @return string
     */
    private function buildCsv(array $headers, array $rows): string {
        $output = self::UTF8_BOM;
        $output .= $this->encodeCsvLine($headers);

        foreach ($rows as $row) {
            $output .= $this->encodeCsvLine($row);
        }

        return $output;
    }

    /**
     * Encode a single CSV line with proper escaping and formula injection prevention.
     *
     * @param string[] $fields
     * @return string
     */
    private function encodeCsvLine(array $fields): string {
        $escaped = array_map(function (string $field): string {
            $field = self::sanitizeCsvField($field);

            // Quote the field if it contains the delimiter, quotes, or newlines
            if (
                str_contains($field, self::DELIMITER) ||
                str_contains($field, '"') ||
                str_contains($field, "\n") ||
                str_contains($field, "\r")
            ) {
                return '"' . str_replace('"', '""', $field) . '"';
            }
            return $field;
        }, $fields);

        return implode(self::DELIMITER, $escaped) . "\r\n";
    }

    /**
     * Prevent CSV injection (DDE/formula injection) by prefixing dangerous
     * starting characters with a single quote.
     *
     * Spreadsheets like Excel interpret fields starting with =, +, -, @
     * as formulas, which can execute commands via DDE.
     *
     * Part of Issue #174.
     */
    public static function sanitizeCsvField(string $field): string {
        if ($field !== '' && in_array($field[0], ['=', '+', '-', '@'], true)) {
            return "'" . $field;
        }
        return $field;
    }

    // ── Formatting helpers ──────────────────────────────────────────

    private function formatGeschlecht(?string $geschlecht): string {
        $map = [
            'm' => 'Maennlich',
            'w' => 'Weiblich',
            'd' => 'Divers',
        ];
        return $map[$geschlecht ?? ''] ?? ($geschlecht ?? '');
    }

    private function formatRolle(string $rolle): string {
        $map = [
            'mitglied' => 'Mitglied',
            'erziehungsberechtigter' => 'Erziehungsberechtigter',
        ];
        return $map[$rolle] ?? $rolle;
    }

    private function formatStatus(string $status): string {
        $map = [
            'aktiv' => 'Aktiv',
            'inaktiv' => 'Inaktiv',
            'geloescht' => 'Geloescht',
        ];
        return $map[$status] ?? $status;
    }
}
