<?php

namespace App\Http\Controllers;

use App\Models\DriverPayout;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\StreamedResponse;
use ZipArchive;

class DriverPayoutController extends Controller
{
    public function index(): View
    {
        $this->mustBeAccount();

        return view('driver-payouts.index', [
            'payouts' => DriverPayout::latest()->paginate(25),
            'grandTotal' => DriverPayout::sum('total_amount'),
        ]);
    }

    public function store(Request $request): RedirectResponse
    {
        $this->mustBeAccount();
        $data = $this->validatePayout($request);
        $data['total_amount'] = DriverPayout::calculateTotal((float) $data['settlement_amount'], (float) ($data['addition'] ?? 0), (float) ($data['deficit'] ?? 0));
        $data['created_by'] = Auth::id();

        DriverPayout::create($data);

        return back()->with('success', 'Driver payout row saved.');
    }

    public function update(Request $request, DriverPayout $driverPayout): RedirectResponse
    {
        $this->mustBeAccount();
        $data = $this->validatePayout($request);
        $data['total_amount'] = DriverPayout::calculateTotal((float) $data['settlement_amount'], (float) ($data['addition'] ?? 0), (float) ($data['deficit'] ?? 0));

        $driverPayout->update($data);

        return back()->with('success', 'Driver payout row updated.');
    }

    public function destroy(DriverPayout $driverPayout): RedirectResponse
    {
        $this->mustBeAccount();
        $driverPayout->delete();

        return back()->with('success', 'Driver payout row deleted.');
    }

    public function export(): StreamedResponse
    {
        $this->mustBeAccount();

        $filename = 'driver-payout-bank-export-'.now()->format('Ymd-His').'.csv';

        return response()->streamDownload(function (): void {
            $handle = fopen('php://output', 'w');
            fputcsv($handle, ['driver name', 'driver bank', 'driver account no', 'total amount']);

            DriverPayout::orderBy('driver_name')->chunk(500, function ($payouts) use ($handle): void {
                foreach ($payouts as $payout) {
                    fputcsv($handle, [
                        $this->csvSafe($payout->driver_name),
                        $this->csvSafe($payout->driver_bank),
                        $this->csvSafe($payout->driver_account_no),
                        number_format((float) $payout->total_amount, 2, '.', ''),
                    ]);
                }
            });

            fclose($handle);
        }, $filename, ['Content-Type' => 'text/csv']);
    }

    public function import(Request $request): RedirectResponse
    {
        $this->mustBeAccount();

        $request->validate([
            'bulk_file' => ['required', 'file', 'max:10240'],
        ]);

        $path = $request->file('bulk_file')->getRealPath();
        $extension = Str::lower($request->file('bulk_file')->getClientOriginalExtension());

        if (! in_array($extension, ['csv', 'txt', 'xlsx'], true)) {
            return back()->withErrors(['bulk_file' => 'Upload a CSV, TXT, or XLSX file.']);
        }
        $rows = $extension === 'xlsx' ? $this->readXlsx($path) : $this->readCsv($path);

        $created = 0;
        $skipped = 0;

        foreach ($rows as $row) {
            $normalized = $this->normalizeImportRow($row);

            if (! $normalized) {
                $skipped++;
                continue;
            }

            $normalized['total_amount'] = DriverPayout::calculateTotal(
                (float) $normalized['settlement_amount'],
                (float) ($normalized['addition'] ?? 0),
                (float) ($normalized['deficit'] ?? 0)
            );
            $normalized['created_by'] = Auth::id();

            DriverPayout::create($normalized);
            $created++;
        }

        return back()->with('success', "Bulk import completed. {$created} row(s) imported, {$skipped} row(s) skipped.");
    }

    private function validatePayout(Request $request): array
    {
        return $request->validate([
            'driver_name' => ['required', 'string', 'max:150'],
            'driver_bank' => ['required', 'string', 'max:150'],
            'driver_account_no' => ['required', 'string', 'max:30'],
            'settlement_amount' => ['required', 'numeric', 'min:0'],
            'payout_date' => ['nullable', 'date'],
            'deficit' => ['nullable', 'numeric', 'min:0'],
            'addition' => ['nullable', 'numeric', 'min:0'],
        ]);
    }

    private function readCsv(string $path): array
    {
        $handle = fopen($path, 'r');
        if (! $handle) {
            return [];
        }

        $headers = null;
        $rows = [];

        while (($line = fgetcsv($handle)) !== false) {
            if ($headers === null) {
                $headers = array_map(fn ($item) => $this->normalizeHeader((string) $item), $line);
                continue;
            }

            if (count(array_filter($line, fn ($value) => trim((string) $value) !== '')) === 0) {
                continue;
            }

            $rows[] = array_combine($headers, array_pad($line, count($headers), null));
        }

        fclose($handle);

        return $rows;
    }

    private function readXlsx(string $path): array
    {
        if (! class_exists(ZipArchive::class) || ! function_exists('simplexml_load_string')) {
            return [];
        }

        $zip = new ZipArchive();
        if ($zip->open($path) !== true) {
            return [];
        }

        $sharedStrings = [];
        $sharedXml = $zip->getFromName('xl/sharedStrings.xml');
        if ($sharedXml) {
            $xml = simplexml_load_string($sharedXml);
            foreach ($xml->si ?? [] as $si) {
                $parts = [];
                if (isset($si->t)) {
                    $parts[] = (string) $si->t;
                }
                foreach ($si->r ?? [] as $run) {
                    $parts[] = (string) ($run->t ?? '');
                }
                $sharedStrings[] = implode('', $parts);
            }
        }

        $sheetXml = $zip->getFromName('xl/worksheets/sheet1.xml');
        $zip->close();

        if (! $sheetXml) {
            return [];
        }

        $sheet = simplexml_load_string($sheetXml);
        $rawRows = [];

        foreach ($sheet->sheetData->row ?? [] as $row) {
            $values = [];
            foreach ($row->c ?? [] as $cell) {
                $ref = (string) $cell['r'];
                $columnIndex = $this->columnIndexFromCellRef($ref);
                $type = (string) $cell['t'];
                $value = (string) ($cell->v ?? '');

                if ($type === 's' && $value !== '') {
                    $value = $sharedStrings[(int) $value] ?? '';
                }

                $values[$columnIndex] = $value;
            }

            if ($values) {
                ksort($values);
                $rawRows[] = array_values($values);
            }
        }

        if ($rawRows === []) {
            return [];
        }

        $headers = array_map(fn ($item) => $this->normalizeHeader((string) $item), array_shift($rawRows));
        $rows = [];
        foreach ($rawRows as $rawRow) {
            $rows[] = array_combine($headers, array_pad($rawRow, count($headers), null));
        }

        return $rows;
    }

    private function normalizeImportRow(array $row): ?array
    {
        $driverName = trim((string) ($row['driver_name'] ?? $row['drivername'] ?? ''));
        $driverBank = trim((string) ($row['driver_bank'] ?? $row['driverbank'] ?? ''));
        $accountNo = trim((string) ($row['driver_account_no'] ?? $row['driveraccountno'] ?? $row['account_no'] ?? ''));
        $settlement = $this->money($row['settlement_amount'] ?? $row['settlement'] ?? null);

        if ($driverName === '' || $driverBank === '' || $accountNo === '' || $settlement === null) {
            return null;
        }

        return [
            'driver_name' => $driverName,
            'driver_bank' => $driverBank,
            'driver_account_no' => $accountNo,
            'settlement_amount' => $settlement,
            'payout_date' => $this->dateOrNull($row['date'] ?? $row['payout_date'] ?? null),
            'deficit' => $this->money($row['deficit'] ?? 0) ?? 0,
            'addition' => $this->money($row['addition'] ?? 0) ?? 0,
        ];
    }

    private function money(mixed $value): ?float
    {
        $clean = str_replace([',', '₦', 'NGN', ' '], '', (string) $value);

        return is_numeric($clean) ? (float) $clean : null;
    }

    private function dateOrNull(mixed $value): ?string
    {
        $value = trim((string) $value);
        if ($value === '') {
            return null;
        }

        if (is_numeric($value)) {
            return date('Y-m-d', strtotime('1899-12-30 +'.((int) $value).' days'));
        }

        $timestamp = strtotime($value);

        return $timestamp ? date('Y-m-d', $timestamp) : null;
    }

    private function normalizeHeader(string $header): string
    {
        return Str::of($header)->lower()->replace([' ', '-', '.'], '_')->replace('__', '_')->trim('_')->toString();
    }

    private function columnIndexFromCellRef(string $cellRef): int
    {
        preg_match('/[A-Z]+/', $cellRef, $matches);
        $letters = $matches[0] ?? 'A';
        $index = 0;
        foreach (str_split($letters) as $letter) {
            $index = ($index * 26) + (ord($letter) - 64);
        }

        return $index - 1;
    }

    private function csvSafe(string $value): string
    {
        $trimmed = trim($value);
        if ($trimmed !== '' && in_array($trimmed[0], ['=', '+', '-', '@'], true)) {
            return "'".$trimmed;
        }

        return $trimmed;
    }

    private function mustBeAccount(): void
    {
        abort_unless(Auth::user()?->isAccount(), 403, 'Only Account users can access driver payout.');
    }
}
