<?php

namespace App\Services;

use App\Exports\GenericExport;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Maatwebsite\Excel\Facades\Excel;
use Symfony\Component\HttpFoundation\BinaryFileResponse;

class ExportService
{
      /**
       * Download data in the given format
       */
      public function download(Collection $data, string $format, string $label, ?string $pdfTitle = null): BinaryFileResponse
      {
            $filename = $label . '-' . now()->format('Y-m-d-His') . '.' . $format;
            $relativePath = 'exports/' . $filename;

            $fullPath = match ($format) {
                  'csv' => $this->generateCsv($data, $relativePath),
                  'xlsx' => $this->generateExcel($data, $relativePath),
                  'pdf' => $this->generatePdf($data, $relativePath, $pdfTitle ?? ucfirst($label)),
                  default => throw new \InvalidArgumentException("Unsupported export format: {$format}"),
            };

            return response()->download($fullPath)->deleteFileAfterSend(true);
      }

      /**
       * Email data in the given format
       */
      public function email(Collection $data, string $format, string $label, string $email, ?string $pdfTitle = null): void
      {
            $filename = $label . '-' . now()->format('Y-m-d-His') . '-' . uniqid() . '.' . $format;
            $relativePath = 'exports/' . $filename;

            $fullPath = match ($format) {
                  'csv' => $this->generateCsv($data, $relativePath),
                  'xlsx' => $this->generateExcel($data, $relativePath),
                  'pdf' => $this->generatePdf($data, $relativePath, $pdfTitle ?? ucfirst($label)),
                  default => throw new \InvalidArgumentException("Unsupported export format: {$format}"),
            };

            if (!Storage::disk('local')->exists($relativePath)) {
                  throw new \RuntimeException("Export file not found at {$fullPath}");
            }

            try {
                  Mail::send(
                        'emails.transactions_export',
                        [
                              'user' => auth()->user(),
                              'format' => $format,
                              'label' => $label,
                        ],
                        function ($message) use ($email, $fullPath, $filename, $format) {
                              $message->to($email)
                                    ->subject("Your Export is Ready")
                                    ->attach($fullPath, [
                                          'as' => $filename,
                                          'mime' => match ($format) {
                                                'csv' => 'text/csv',
                                                'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                                                'pdf' => 'application/pdf',
                                                default => 'application/octet-stream',
                                          },
                                    ]);
                        }
                  );
            } finally {
                  Storage::disk('local')->delete($relativePath);
            }
      }

      // =========================================================
      // GENERATORS
      // =========================================================

      protected function generateCsv(Collection $data, string $relativePath): string
      {
            Storage::disk('local')->makeDirectory('exports');

            $fullPath = Storage::disk('local')->path($relativePath);
            $file = fopen($fullPath, 'w');

            if ($data->isEmpty()) {
                  fclose($file);
                  return $fullPath;
            }

            [$rows, $headings] = $this->prepareRows($data);

            // headings
            fputcsv($file, array_map(fn($h) => $this->titleHeading($h), $headings));

            foreach ($rows as $row) {
                  $line = [];
                  foreach ($headings as $key) {
                        $line[] = $row[$key] ?? '';
                  }
                  fputcsv($file, $line);
            }

            fclose($file);

            return $fullPath;
      }

      protected function generateExcel(Collection $data, string $relativePath): string
      {
            Storage::disk('local')->makeDirectory('exports');

            [$rows, $headings] = $this->prepareRows($data);

            $titles = array_map(fn($h) => $this->titleHeading($h), $headings);

            Excel::store(
                  new GenericExport(collect($rows), $titles),
                  $relativePath,
                  'local'
            );

            return Storage::disk('local')->path($relativePath);
      }

      protected function generatePdf(Collection $data, string $relativePath, string $title = 'Exported Data'): string
      {
            Storage::disk('local')->makeDirectory('exports');

            [$rows, $headings] = $this->prepareRows($data);

            $fullPath = Storage::disk('local')->path($relativePath);

            $pdf = Pdf::loadView('exports.generic', [
                  'title' => $title,
                  'rows' => collect($rows),
                  'keys' => $headings, // raw keys for row access
                  'headings' => array_map(fn($h) => $this->titleHeading($h), $headings), // display labels
            ])->setPaper('a4', count($headings) > 8 ? 'landscape' : 'portrait');

            file_put_contents($fullPath, $pdf->output());

            return $fullPath;
      }

      // =========================================================
      // DATA PREP
      // =========================================================

      /**
       * Prepare rows + headings
       */
      protected function prepareRows(Collection $data): array
      {
            $flatRows = $data->map(function ($row) {

                  if ($row instanceof \Illuminate\Database\Eloquent\Model) {
                        $arr = $row->getAttributes();
                  } else {
                        $arr = is_array($row) ? $row : (array) $row;
                  }

                  return $this->flatten($arr);

            })->values();

            if ($flatRows->isEmpty()) {
                  return [[], []];
            }

            // Collect all keys that actually have at least one non-empty value
            $allKeys = $flatRows
                  ->flatMap(fn($row) => array_keys($row))
                  ->unique()
                  ->values()
                  ->filter(function ($key) use ($flatRows) {
                        return $flatRows->contains(fn($row) => isset($row[$key]) && $row[$key] !== '' && $row[$key] !== null);
                  })
                  ->values()
                  ->toArray();

            // sort deterministically
            sort($allKeys);

            // normalize rows
            $rows = $flatRows->map(function ($row) use ($allKeys) {
                  return collect($allKeys)->mapWithKeys(
                        fn($key) => [$key => $row[$key] ?? '']
                  )->toArray();
            })->toArray();

            return [$rows, $allKeys];
      }


      /**
       * Flatten nested arrays into key.subkey
       */
      protected function flatten(array $array, string $prefix = ''): array
      {
            $result = [];

            foreach ($array as $key => $value) {
                  $newKey = $prefix === '' ? $key : $prefix . '.' . $key;

                  if (is_array($value)) {
                        // If it's an associative array, flatten recursively
                        if ($this->isAssoc($value)) {
                              $result += $this->flatten($value, $newKey);
                        } else {
                              // If it's a numeric array, join for readability
                              $result[$newKey] = implode(', ', array_map(fn($v) => $this->humanize($v), $value));
                        }
                  } elseif (is_object($value)) {
                        $result += $this->flatten((array) $value, $newKey);
                  } else {
                        $result[$newKey] = $this->humanize($value);
                  }
            }

            return $result;
      }

      // Helper: detect associative arrays
      protected function isAssoc(array $arr): bool
      {
            return array_keys($arr) !== range(0, count($arr) - 1);
      }

      // Helper: humanize string values (optional, e.g., program_funding -> Program Funding)
      protected function humanize(mixed $value): string
      {
            if (!is_string($value))
                  return (string) $value;

            return str($value)
                  ->replace('_', ' ')
                  ->title()
                  ->toString();
      }


      protected function titleHeading(string $key): string
      {
            $key = str($key)->afterLast('.');

            return $key
                  ->replace('_', ' ')
                  ->title()
                  ->toString();
      }
}
