<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Service;

use DateTime;
use OCA\Mitgliederverwaltung\Db\AddressMapper;
use OCA\Mitgliederverwaltung\Db\EmailMapper;
use OCA\Mitgliederverwaltung\Db\FamilyMapper;
use OCA\Mitgliederverwaltung\Db\FeeRecordMapper;
use OCA\Mitgliederverwaltung\Db\InjuryMapper;
use OCA\Mitgliederverwaltung\Db\LagerMapper;
use OCA\Mitgliederverwaltung\Db\LagerTeilnehmerMapper;
use OCA\Mitgliederverwaltung\Db\MemberMapper;
use OCA\Mitgliederverwaltung\Db\PhoneMapper;
use OCA\Mitgliederverwaltung\Db\StufeMapper;
use OCP\DB\Exception;
use Psr\Log\LoggerInterface;

/**
 * Report data generation service.
 *
 * Each method returns structured data as an array of rows with headers,
 * suitable for both PDF rendering and preview display.
 *
 * Part of Issue #46.
 */
class ReportService {

    private MemberMapper $memberMapper;
    private AddressMapper $addressMapper;
    private PhoneMapper $phoneMapper;
    private EmailMapper $emailMapper;
    private FamilyMapper $familyMapper;
    private FeeRecordMapper $feeRecordMapper;
    private StufeMapper $stufeMapper;
    private LagerMapper $lagerMapper;
    private LagerTeilnehmerMapper $teilnehmerMapper;
    private InjuryMapper $injuryMapper;
    private EncryptionService $encryptionService;
    private LoggerInterface $logger;

    public function __construct(
        MemberMapper $memberMapper,
        AddressMapper $addressMapper,
        PhoneMapper $phoneMapper,
        EmailMapper $emailMapper,
        FamilyMapper $familyMapper,
        FeeRecordMapper $feeRecordMapper,
        StufeMapper $stufeMapper,
        LagerMapper $lagerMapper,
        LagerTeilnehmerMapper $teilnehmerMapper,
        InjuryMapper $injuryMapper,
        EncryptionService $encryptionService,
        LoggerInterface $logger
    ) {
        $this->memberMapper = $memberMapper;
        $this->addressMapper = $addressMapper;
        $this->phoneMapper = $phoneMapper;
        $this->emailMapper = $emailMapper;
        $this->familyMapper = $familyMapper;
        $this->feeRecordMapper = $feeRecordMapper;
        $this->stufeMapper = $stufeMapper;
        $this->lagerMapper = $lagerMapper;
        $this->teilnehmerMapper = $teilnehmerMapper;
        $this->injuryMapper = $injuryMapper;
        $this->encryptionService = $encryptionService;
        $this->logger = $logger;
    }

    /**
     * Mitgliederliste: all (or filtered) members with basic info.
     *
     * @return array{title: string, headers: string[], rows: array[]}
     * @throws Exception
     */
    public function generateMitgliederliste(?string $statusFilter = null): array {
        if ($statusFilter !== null && $statusFilter !== '') {
            $members = $this->memberMapper->findByStatus($statusFilter);
        } else {
            $members = $this->memberMapper->findAll();
        }

        $stufen = $this->getStufeMap();

        $rows = [];
        foreach ($members as $member) {
            $stufeName = $member->getStufeId() !== null
                ? ($stufen[$member->getStufeId()] ?? '–')
                : '–';

            $rows[] = [
                $member->getNachname(),
                $member->getVorname(),
                $this->formatDate($member->getGeburtsdatum()),
                $stufeName,
                $this->formatStatus($member->getStatus()),
                $this->formatDate($member->getEintritt()),
            ];
        }

        $filterLabel = $statusFilter ? " ({$this->formatStatus($statusFilter)})" : '';

        return [
            'title' => 'Mitgliederliste' . $filterLabel,
            'headers' => ['Nachname', 'Vorname', 'Geburtsdatum', 'Stufe', 'Status', 'Eintritt'],
            'rows' => $rows,
        ];
    }

    /**
     * Beitragsliste: fee overview for a given year.
     *
     * @return array{title: string, headers: string[], rows: array[]}
     * @throws Exception
     */
    public function generateBeitragsliste(int $year): array {
        $records = $this->feeRecordMapper->findByYear($year);

        $rows = [];
        foreach ($records as $record) {
            $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[] = [
                $memberName,
                $memberVorname,
                number_format((float)($record->getAmount() ?? 0), 2, ',', '.') . ' EUR',
                $record->getPaid() ? 'Ja' : 'Nein',
                $record->getPaymentDate() ? $this->formatDate($record->getPaymentDate()) : '–',
            ];
        }

        return [
            'title' => "Beitragsliste {$year}",
            'headers' => ['Nachname', 'Vorname', 'Betrag', 'Bezahlt', 'Zahlungsdatum'],
            'rows' => $rows,
        ];
    }

    /**
     * Stufenliste: members grouped by Stufe.
     *
     * @return array{title: string, headers: string[], rows: array[]}
     * @throws Exception
     */
    public function generateStufenliste(): array {
        $stufen = $this->stufeMapper->findAll();
        $rows = [];

        foreach ($stufen as $stufe) {
            $members = $this->memberMapper->findByStufe($stufe->getId());

            if (count($members) === 0) {
                $rows[] = [$stufe->getName(), '–', '–', '–', '0'];
                continue;
            }

            foreach ($members as $i => $member) {
                $rows[] = [
                    $i === 0 ? $stufe->getName() : '',
                    $member->getNachname(),
                    $member->getVorname(),
                    $this->formatDate($member->getGeburtsdatum()),
                    $i === 0 ? (string)count($members) : '',
                ];
            }
        }

        // Also include members without Stufe
        $allMembers = $this->memberMapper->findAll();
        $noStufe = array_filter($allMembers, fn($m) => $m->getStufeId() === null);
        foreach ($noStufe as $i => $member) {
            $rows[] = [
                $i === 0 ? 'Keine Stufe' : '',
                $member->getNachname(),
                $member->getVorname(),
                $this->formatDate($member->getGeburtsdatum()),
                $i === 0 ? (string)count($noStufe) : '',
            ];
        }

        return [
            'title' => 'Stufenliste',
            'headers' => ['Stufe', 'Nachname', 'Vorname', 'Geburtsdatum', 'Anzahl'],
            'rows' => $rows,
        ];
    }

    /**
     * Allergieliste: members with allergies and health insurance info.
     *
     * @return array{title: string, headers: string[], rows: array[]}
     * @throws Exception
     */
    public function generateAllergieliste(): array {
        $members = $this->memberMapper->findAll();
        $rows = [];

        foreach ($members as $member) {
            $allergien = $this->encryptionService->decrypt($member->getAllergienEncrypted());

            // Skip members without allergies or KV info
            if (($allergien === null || $allergien === '') && $member->getKvTyp() === null) {
                continue;
            }

            $rows[] = [
                $member->getNachname(),
                $member->getVorname(),
                $allergien ?? '–',
                $member->getKvTyp() ?? '–',
                $member->getKvName() ?? '–',
            ];
        }

        return [
            'title' => 'Allergieliste',
            'headers' => ['Nachname', 'Vorname', 'Allergien', 'KV-Typ', 'KV-Name'],
            'rows' => $rows,
        ];
    }

    /**
     * Geburtstagsliste: upcoming birthdays within N months.
     *
     * @return array{title: string, headers: string[], rows: array[]}
     * @throws Exception
     */
    public function generateGeburtstagsliste(int $months = 3): array {
        $now = new DateTime();
        $currentMonth = (int)$now->format('m');
        $rows = [];

        for ($i = 0; $i < $months; $i++) {
            $month = (($currentMonth - 1 + $i) % 12) + 1;
            $members = $this->memberMapper->findByBirthdayMonth($month);

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

                $rows[] = [
                    $member->getNachname(),
                    $member->getVorname(),
                    $this->formatDate($member->getGeburtsdatum()),
                    (string)$age,
                    $this->getMonthName($month),
                ];
            }
        }

        return [
            'title' => "Geburtstagsliste (naechste {$months} Monate)",
            'headers' => ['Nachname', 'Vorname', 'Geburtsdatum', 'Alter', 'Monat'],
            'rows' => $rows,
        ];
    }

    /**
     * Kontaktliste: addresses and phone numbers, optionally filtered by Stufe.
     *
     * @return array{title: string, headers: string[], rows: array[]}
     * @throws Exception
     */
    public function generateKontaktliste(?int $stufeId = null): array {
        if ($stufeId !== null) {
            $members = $this->memberMapper->findByStufe($stufeId);
        } else {
            $members = $this->memberMapper->findAll();
        }

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

            $primaryAddr = null;
            foreach ($addresses as $addr) {
                if ($addr->getIsPrimary() || $primaryAddr === null) {
                    $primaryAddr = $addr;
                }
            }

            $rows[] = [
                $member->getNachname(),
                $member->getVorname(),
                $primaryAddr ? $primaryAddr->getStrasse() : '–',
                $primaryAddr ? ($primaryAddr->getPlz() . ' ' . $primaryAddr->getOrt()) : '–',
                count($phones) > 0 ? $phones[0]->getNumberE164() : '–',
                count($emails) > 0 ? $emails[0]->getEmail() : '–',
            ];
        }

        $title = 'Kontaktliste';
        if ($stufeId !== null) {
            try {
                $stufe = $this->stufeMapper->findById($stufeId);
                $title .= ' – ' . $stufe->getName();
            } catch (\Exception $e) {
                // Stufe may not exist
            }
        }

        return [
            'title' => $title,
            'headers' => ['Nachname', 'Vorname', 'Strasse', 'PLZ/Ort', 'Telefon', 'E-Mail'],
            'rows' => $rows,
        ];
    }

    /**
     * Bankverbindungen: IBAN list (restricted, requires banking permission).
     *
     * @return array{title: string, headers: string[], rows: array[]}
     * @throws Exception
     */
    public function generateBankverbindungen(): array {
        $families = $this->familyMapper->findAll();
        $rows = [];

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

            $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),
            ];
        }

        return [
            'title' => 'Bankverbindungen',
            'headers' => ['Familie', 'Kontoinhaber', 'IBAN', 'BIC', 'Kreditinstitut', 'Mitglieder'],
            'rows' => $rows,
        ];
    }

    /**
     * Familienliste: families with their members.
     *
     * @return array{title: string, headers: string[], rows: array[]}
     * @throws Exception
     */
    public function generateFamilienliste(): array {
        $families = $this->familyMapper->findAll();
        $rows = [];

        foreach ($families as $family) {
            $members = $this->memberMapper->findByFamily($family->getId());

            if (count($members) === 0) {
                $rows[] = [$family->getName(), '–', '–', '0'];
                continue;
            }

            foreach ($members as $i => $member) {
                $rows[] = [
                    $i === 0 ? $family->getName() : '',
                    $member->getVorname() . ' ' . $member->getNachname(),
                    $this->formatRolle($member->getRolle()),
                    $i === 0 ? (string)count($members) : '',
                ];
            }
        }

        return [
            'title' => 'Familienliste',
            'headers' => ['Familie', 'Mitglied', 'Rolle', 'Anzahl'],
            'rows' => $rows,
        ];
    }

    /**
     * Lagerhistorie: which camps each member attended, with dates and roles.
     *
     * Part of Issue #129 / Req 2.6.
     *
     * @return array{title: string, headers: string[], rows: array[]}
     * @throws Exception
     */
    public function generateLagerhistorie(?int $memberId = null): array {
        $camps = $this->lagerMapper->findAll();
        $rows = [];

        foreach ($camps as $camp) {
            $participants = $this->teilnehmerMapper->findByLagerId($camp->getId());

            foreach ($participants as $participant) {
                // If filtering by member, skip non-matching
                if ($memberId !== null && $participant->getMemberId() !== $memberId) {
                    continue;
                }

                $memberName = 'Unbekannt';
                $memberVorname = '';
                try {
                    $member = $this->memberMapper->findById($participant->getMemberId());
                    $memberName = $member->getNachname();
                    $memberVorname = $member->getVorname();
                } catch (\Exception $e) {
                    // Member may have been deleted
                }

                $rows[] = [
                    $memberName,
                    $memberVorname,
                    $camp->getName(),
                    $this->formatDate($camp->getStartdatum()),
                    $this->formatDate($camp->getEnddatum()),
                    $camp->getOrt() ?? '–',
                    $participant->getRolle(),
                ];
            }
        }

        $title = 'Lagerhistorie';
        if ($memberId !== null) {
            try {
                $member = $this->memberMapper->findById($memberId);
                $title .= ' – ' . $member->getVorname() . ' ' . $member->getNachname();
            } catch (\Exception $e) {
                // ignore
            }
        }

        return [
            'title' => $title,
            'headers' => ['Nachname', 'Vorname', 'Lager', 'Von', 'Bis', 'Ort', 'Rolle'],
            'rows' => $rows,
        ];
    }

    /**
     * Verletzungsprotokoll: injury records for a given period or member.
     *
     * Part of Issue #129 / Req 2.6.
     *
     * @return array{title: string, headers: string[], rows: array[]}
     * @throws Exception
     */
    public function generateVerletzungsprotokoll(?string $dateFrom = null, ?string $dateTo = null, ?int $memberId = null): array {
        if ($memberId !== null) {
            $injuries = $this->injuryMapper->findByMemberId($memberId);
        } elseif ($dateFrom !== null && $dateTo !== null) {
            $injuries = $this->injuryMapper->findByDateRange($dateFrom, $dateTo);
        } else {
            $injuries = $this->injuryMapper->findAll();
        }

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

            $lagerName = '–';
            if ($injury->getLagerId() !== null) {
                try {
                    $lager = $this->lagerMapper->findById($injury->getLagerId());
                    $lagerName = $lager->getName();
                } catch (\Exception $e) {
                    // Camp may have been deleted
                }
            }

            $rows[] = [
                $this->formatDate($injury->getDatum()),
                $memberName,
                $memberVorname,
                $injury->getBeschreibung(),
                $lagerName,
                $injury->getActivityName() ?? '–',
            ];
        }

        $title = 'Verletzungsprotokoll';
        if ($dateFrom !== null && $dateTo !== null) {
            $title .= " ({$this->formatDate($dateFrom)} – {$this->formatDate($dateTo)})";
        } elseif ($memberId !== null) {
            try {
                $member = $this->memberMapper->findById($memberId);
                $title .= ' – ' . $member->getVorname() . ' ' . $member->getNachname();
            } catch (\Exception $e) {
                // ignore
            }
        }

        return [
            'title' => $title,
            'headers' => ['Datum', 'Nachname', 'Vorname', 'Beschreibung', 'Lager', 'Aktivitaet'],
            'rows' => $rows,
        ];
    }

    // ── Helpers ─────────────────────────────────────────────────────

    /**
     * Build a map of stufeId => stufeName.
     *
     * @return array<int, string>
     */
    private function getStufeMap(): array {
        $stufen = $this->stufeMapper->findAll();
        $map = [];
        foreach ($stufen as $stufe) {
            $map[$stufe->getId()] = $stufe->getName();
        }
        return $map;
    }

    private function formatDate(?string $date): string {
        if ($date === null || $date === '') {
            return '–';
        }
        try {
            return (new DateTime($date))->format('d.m.Y');
        } catch (\Exception $e) {
            return $date;
        }
    }

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

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

    private function getMonthName(int $month): string {
        $names = [
            1 => 'Januar', 2 => 'Februar', 3 => 'Maerz', 4 => 'April',
            5 => 'Mai', 6 => 'Juni', 7 => 'Juli', 8 => 'August',
            9 => 'September', 10 => 'Oktober', 11 => 'November', 12 => 'Dezember',
        ];
        return $names[$month] ?? 'Monat';
    }
}
