<?php

declare(strict_types=1);

namespace App\Services;

use PharData;
use RuntimeException;
use Throwable;
use ZipArchive;

final class PowerPointDailyRevenueExportService
{
    /** @param array<string,mixed> $report */
    public function stream(array $report, string $filename): void
    {
        $tmpBase = tempnam(sys_get_temp_dir(), 'ddb_ppt_');
        if ($tmpBase === false) { throw new RuntimeException('Unable to create PowerPoint file.'); }
        @unlink($tmpBase);
        $tmp = $tmpBase . '.pptx';
        try {
            $this->create($tmp, $report);
            header('Content-Type: application/vnd.openxmlformats-officedocument.presentationml.presentation');
            header('Content-Disposition: attachment; filename="' . preg_replace('/[^A-Za-z0-9._-]/', '_', $filename) . '"');
            header('Content-Length: ' . (string)filesize($tmp));
            header('Cache-Control: private, max-age=0, must-revalidate');
            readfile($tmp);
        } finally { @unlink($tmp); }
    }

    /** @param array<string,mixed> $report */
    public function create(string $path, array $report): void
    {
        $template = base_path('resources/templates/daily_revenue_brief_template.pptx');
        if (!is_file($template)) { throw new RuntimeException('PowerPoint template is missing.'); }
        $replacements = $this->buildReplacements($report);
        $entries = $this->readZipPackage($template);
        foreach ($entries as $name => $content) {
            if (preg_match('#^(ppt/slides/slide[0-9]+\.xml|docProps/core\.xml|docProps/app\.xml)$#', $name) === 1) {
                $entries[$name] = strtr($content, $replacements);
            }
        }
        $this->writeZipPackage($path, $entries);
    }

    /** @param array<string,mixed> $report @return array<string,string> */
    private function buildReplacements(array $report): array
    {
        $summary = is_array($report['summary'] ?? null) ? $report['summary'] : [];
        $hospital = trim((string)($report['hospital_name'] ?? $report['hospital_code'] ?? 'Hospital'));
        $hospitalCode = trim((string)($report['hospital_code'] ?? $hospital));
        $generated = (string)($summary['generated_at'] ?? date('Y-m-d H:i:s'));
        $patients = (int)($summary['patient_count'] ?? 0);
        $totalRevenue = (float)($summary['daily_revenue'] ?? 0);
        $avgRevenue = (float)($summary['average_revenue'] ?? 0);
        $review = (int)($summary['review_count'] ?? 0);

        $r = [
            '[[HOSPITAL]]' => $this->xml($hospitalCode !== '' ? $hospitalCode : $hospital),
            '[[BRANCH]]' => $this->xml($hospital),
            '[[GENERATED_AT]]' => $this->xml($this->dateText($generated)),
            '[[PATIENTS_IN_ROOMS]]' => (string)$patients,
            '[[TOTAL_DAILY_REVENUE]]' => $this->money($totalRevenue),
            '[[AVERAGE_PER_PATIENT]]' => $this->money($avgRevenue),
            '[[NEEDS_REVIEW]]' => (string)$review,
        ];

        $companies = array_values(array_filter((array)($report['companies'] ?? []), 'is_array'));
        $total = $totalRevenue > 0 ? $totalRevenue : 1;
        $topValue = isset($companies[0]) && is_array($companies[0]) ? (float)($companies[0]['daily_revenue'] ?? 0) : 0.0;
        $topName = isset($companies[0]) && is_array($companies[0]) ? trim((string)($companies[0]['financial_class'] ?? '')) : '';
        $r['[[TOP_PAYER_SHARE]]'] = $topValue > 0 ? number_format(($topValue / $total) * 100, 1) . '%' : 'N/A';
        $r['[[TOP_PAYER_NAME]]'] = $this->xml($topName);
        for ($i = 1; $i <= 5; $i++) {
            $c = $companies[$i - 1] ?? [];
            $value = (float)($c['daily_revenue'] ?? 0);
            $share = $value > 0 ? ($value / $total) * 100 : 0;
            $r['[[PAYER_' . $i . '_NAME]]'] = $this->xml((string)($c['financial_class'] ?? ''));
            $r['[[PAYER_' . $i . '_REVENUE]]'] = $value > 0 ? $this->money($value) : '';
            $r['[[PAYER_' . $i . '_PATIENTS]]'] = isset($c['patients']) ? (string)(int)$c['patients'] : '';
            $r['[[PAYER_' . $i . '_SHARE]]'] = $value > 0 ? number_format($share, 1) . '%' : '';
        }

        $units = array_values(array_filter((array)($report['units'] ?? []), 'is_array'));
        for ($i = 1; $i <= 6; $i++) {
            $u = $units[$i - 1] ?? [];
            $value = (float)($u['daily_revenue'] ?? 0);
            $beds = (int)($u['beds'] ?? 0);
            $count = (int)($u['patients'] ?? 0);
            $occ = $beds > 0 ? ($count / $beds) * 100 : 0;
            $r['[[UNIT_' . $i . '_NAME]]'] = $this->xml((string)($u['unit_name'] ?? ''));
            $r['[[UNIT_' . $i . '_OCCUPANCY]]'] = $beds > 0 ? ((string)$count . '/' . (string)$beds . ' beds · ' . number_format($occ, 1) . '%') : '';
            $r['[[UNIT_' . $i . '_REVENUE]]'] = $value > 0 ? $this->money($value) : '';
        }

        $patientRows = array_values(array_filter((array)($report['patients'] ?? []), 'is_array'));
        usort($patientRows, static function ($a, $b): int { return ((float)($b['daily_revenue'] ?? 0)) <=> ((float)($a['daily_revenue'] ?? 0)); });
        for ($i = 1; $i <= 5; $i++) {
            $p = $patientRows[$i - 1] ?? [];
            $r['[[PATIENT_' . $i . '_NAME]]'] = $this->xml((string)($p['patient_name'] ?? ''));
            $r['[[PATIENT_' . $i . '_LOCATION]]'] = $this->xml(trim((string)($p['room'] ?? '') . ' / ' . (string)($p['bed'] ?? '')));
            $r['[[PATIENT_' . $i . '_PAYER]]'] = $this->xml((string)($p['financial_class'] ?? ''));
            $r['[[PATIENT_' . $i . '_REVENUE]]'] = isset($p['daily_revenue']) ? $this->money((float)$p['daily_revenue']) : '';
        }
        return $r;
    }

    private function money(float $value): string { return number_format($value, 2); }
    private function dateText(string $value): string
    {
        $ts = strtotime($value);
        return $ts ? date('d M Y h:i A', $ts) : $value;
    }
    private function xml(string $value): string { return htmlspecialchars($value, ENT_XML1 | ENT_COMPAT, 'UTF-8'); }

    /** @return array<string,string> */
    private function readZipPackage(string $path): array
    {
        if (class_exists(ZipArchive::class)) {
            $zip = new ZipArchive();
            if ($zip->open($path) !== true) { throw new RuntimeException('Unable to open PowerPoint template.'); }
            $entries = [];
            for ($i = 0; $i < $zip->numFiles; $i++) {
                $name = (string)$zip->getNameIndex($i);
                $content = $zip->getFromIndex($i);
                if ($content === false) { $zip->close(); throw new RuntimeException('Unable to read PowerPoint template.'); }
                if (substr($name, -1) !== '/') { $entries[$name] = $content; }
            }
            $zip->close();
            return $entries;
        }
        if (class_exists(PharData::class)) {
            try {
                $archive = new PharData($path);
                $entries = [];
                $it = new \RecursiveIteratorIterator($archive, \RecursiveIteratorIterator::SELF_FIRST);
                $prefix = 'phar://' . str_replace('\\', '/', $path) . '/';
                foreach ($it as $file) {
                    if ($file->isDir()) { continue; }
                    $full = str_replace('\\', '/', $file->getPathName());
                    $name = strpos($full, $prefix) === 0 ? substr($full, strlen($prefix)) : $file->getFilename();
                    $content = @file_get_contents($file->getPathName());
                    if ($content === false) { throw new RuntimeException('Unable to read PowerPoint template entry.'); }
                    $entries[$name] = $content;
                }
                return $entries;
            } catch (Throwable $e) {
                throw new RuntimeException('Unable to read PowerPoint template.', 0, $e);
            }
        }
        throw new RuntimeException('A ZIP-capable PHP archive engine is required.');
    }

    /** @param array<string,string> $entries */
    private function writeZipPackage(string $path, array $entries): void
    {
        @unlink($path);
        if (class_exists(ZipArchive::class)) {
            $zip = new ZipArchive();
            if ($zip->open($path, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) { throw new RuntimeException('Unable to create PowerPoint file.'); }
            foreach ($entries as $name => $content) { $zip->addFromString($name, $content); }
            $zip->close();
            return;
        }
        if (class_exists(PharData::class)) {
            $zipPath = $path . '.zip';
            @unlink($zipPath);
            try {
                $archive = new PharData($zipPath);
                foreach ($entries as $name => $content) { $archive[$name] = $content; }
                unset($archive);
                if (!@rename($zipPath, $path)) { throw new RuntimeException('Unable to finalize PowerPoint file.'); }
                return;
            } catch (Throwable $e) {
                @unlink($zipPath); @unlink($path);
                throw new RuntimeException('Unable to create PowerPoint file.', 0, $e);
            }
        }
        throw new RuntimeException('A ZIP-capable PHP archive engine is required.');
    }
}
