<?php

declare(strict_types=1);

namespace App\Http\Controllers\Customer;

use App\Domain\Account\Models\Account;
use App\Domain\Domain\Models\Domain;
use App\Domain\MailLogs\Services\MailLogReader;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Inertia\Inertia;
use Inertia\Response;

/**
 * Customer Email Tracking / Track Delivery (cPanel Track Delivery muadili).
 *
 * Customer kendi domain'lerine ait Postfix mail log satırlarını görür.
 * MailLogReader Linux'ta /var/log/maillog veya /var/log/mail.log dosyasını
 * tail eder. Dev (Windows) ortamlarında log yoksa boş döner + warning gösterilir.
 */
class EmailTrackingController extends Controller
{
    private const TAIL_LINES = 1500;

    public function __construct(
        private readonly MailLogReader $logReader,
    ) {}

    public function index(Request $request): Response
    {
        /** @var Account $account */
        $account = $request->attributes->get('customer_account');

        $customerDomains = Domain::where('account_id', $account->id)->pluck('name')->toArray();
        $logInfo = $this->logReader->info('mail');

        $entries = $this->fetchEntries($customerDomains);
        $filtered = $this->applyFilters($entries, $request);

        usort($filtered, static fn (array $a, array $b): int => strcmp((string) $b['timestamp'], (string) $a['timestamp']));

        $stats = $this->buildStats($filtered);
        $perPage = 25;
        $page = max(1, $request->integer('page', 1));
        $total = count($filtered);
        $items = array_slice($filtered, ($page - 1) * $perPage, $perPage);

        $pagination = [
            'current_page' => $page,
            'per_page' => $perPage,
            'total' => $total,
            'last_page' => max(1, (int) ceil($total / $perPage)),
        ];

        $filters = $request->only(['status', 'from', 'to', 'sender', 'recipient', 'domain', 'page']);
        $available_domains = $customerDomains;

        $log_status = [
            'available' => $logInfo['readable'] ?? false,
            'path' => $logInfo['path'] ?? null,
            'message' => ($logInfo['readable'] ?? false)
                ? null
                : "Mail log dosyası bu sunucuda mevcut değil. Gerçek mail tracking yalnızca Linux production hostingda görüntülenir.",
        ];

        return Inertia::render('Customer/Email/TrackDelivery', compact(
            'items',
            'pagination',
            'stats',
            'filters',
            'available_domains',
            'log_status',
        ));
    }

    public function export(Request $request): \Illuminate\Http\Response
    {
        /** @var Account $account */
        $account = $request->attributes->get('customer_account');

        $customerDomains = Domain::where('account_id', $account->id)->pluck('name')->toArray();

        $entries = $this->fetchEntries($customerDomains);
        $filtered = $this->applyFilters($entries, $request);

        usort($filtered, static fn (array $a, array $b): int => strcmp((string) $b['timestamp'], (string) $a['timestamp']));

        $rows = [];
        $rows[] = implode(',', ['timestamp', 'sender', 'recipient', 'subject', 'status', 'smtp_code', 'smtp_message', 'size_bytes', 'host']);

        foreach ($filtered as $e) {
            $rows[] = implode(',', [
                '"' . $e['timestamp'] . '"',
                '"' . $e['sender'] . '"',
                '"' . $e['recipient'] . '"',
                '"' . str_replace('"', '""', (string) $e['subject']) . '"',
                '"' . $e['status'] . '"',
                '"' . $e['smtp_code'] . '"',
                '"' . str_replace('"', '""', (string) $e['smtp_message']) . '"',
                (int) $e['size_bytes'],
                '"' . $e['host'] . '"',
            ]);
        }

        $csv = implode("\n", $rows);

        return response($csv)
            ->header('Content-Type', 'text/csv')
            ->header('Content-Disposition', 'attachment; filename="mail-tracking-' . now()->format('Y-m-d') . '.csv"');
    }

    public function detail(Request $request, int $mailId): Response
    {
        /** @var Account $account */
        $account = $request->attributes->get('customer_account');

        $customerDomains = Domain::where('account_id', $account->id)->pluck('name')->toArray();
        $entries = $this->fetchEntries($customerDomains);
        $entry = $entries[$mailId - 1] ?? null;

        if ($entry === null) {
            $trace = [];
        } else {
            $trace = $this->buildTrace($entry);
        }

        return Inertia::render('Customer/Email/TrackDelivery', [
            'detail_mail_id' => $mailId,
            'detail_mail_trace' => $trace,
            'items' => [],
            'pagination' => ['current_page' => 1, 'per_page' => 25, 'total' => 0, 'last_page' => 1],
            'stats' => ['delivered' => 0, 'sent' => 0, 'deferred' => 0, 'bounced' => 0, 'rejected' => 0, 'total' => 0],
            'filters' => [],
            'available_domains' => $customerDomains,
        ]);
    }

    /**
     * Postfix log satırlarını oku + customer'ın domain'lerine ait olanları parse et.
     *
     * @param  array<int, string>  $customerDomains
     * @return array<int, array<string, mixed>>
     */
    private function fetchEntries(array $customerDomains): array
    {
        if ($customerDomains === []) {
            return [];
        }

        $rawLines = $this->logReader->tail('mail', self::TAIL_LINES);
        if ($rawLines === []) {
            return [];
        }

        $lowerDomains = array_map('strtolower', $customerDomains);
        $entries = [];
        $id = 1;

        foreach ($rawLines as $line) {
            $parsed = $this->parsePostfixLine($line['raw'] ?? '', $line['ts'] ?? null);
            if ($parsed === null) {
                continue;
            }

            $senderDomain = strtolower(self::domainPart($parsed['sender']));
            // Customer sadece kendi domain'lerinden çıkan veya kendi domain'lerine gelen mailleri görür
            $recipientDomain = strtolower(self::domainPart($parsed['recipient']));
            if (! in_array($senderDomain, $lowerDomains, true)
                && ! in_array($recipientDomain, $lowerDomains, true)) {
                continue;
            }

            $parsed['id'] = $id++;
            $entries[] = $parsed;
        }

        return $entries;
    }

    /**
     * Postfix syslog satırını parse et.
     *
     * Beklenen örnek (Postfix smtp/qmgr):
     *   May 14 12:00:01 host postfix/smtp[1234]: 4AB1234: to=<user@gmail.com>, relay=...,
     *     delay=2.1, status=sent (250 2.0.0 OK)
     *
     * @return array<string, mixed>|null
     */
    private function parsePostfixLine(string $raw, ?string $ts): ?array
    {
        if (! str_contains($raw, 'postfix/')) {
            return null;
        }
        if (! preg_match('/\bto=<([^>]+)>/i', $raw, $toMatch)) {
            return null;
        }
        $recipient = $toMatch[1];

        $sender = '';
        if (preg_match('/\bfrom=<([^>]+)>/i', $raw, $fromMatch)) {
            $sender = $fromMatch[1];
        }

        $status = 'sent';
        if (preg_match('/\bstatus=(\w+)/i', $raw, $statusMatch)) {
            $status = strtolower($statusMatch[1]);
        } elseif (preg_match('/\b(reject|bounced|deferred)\b/i', $raw, $m)) {
            $status = strtolower($m[1]);
        }

        // Normalise to known set
        $status = match (true) {
            in_array($status, ['sent', 'delivered'], true) => 'delivered',
            str_starts_with($status, 'bounce') => 'bounced',
            str_starts_with($status, 'defer') => 'deferred',
            str_starts_with($status, 'reject') => 'rejected',
            default => $status,
        };

        $smtpCode = '250';
        $smtpMessage = 'OK';
        if (preg_match('/\((\d{3})\s+([^)]+)\)/', $raw, $codeMatch)) {
            $smtpCode = $codeMatch[1];
            $smtpMessage = trim($codeMatch[2]);
        }

        $delay = 0;
        if (preg_match('/\bdelay=([\d.]+)/i', $raw, $delayMatch)) {
            $delay = (float) $delayMatch[1];
        }

        $sizeBytes = 0;
        if (preg_match('/\bsize=(\d+)/i', $raw, $sizeMatch)) {
            $sizeBytes = (int) $sizeMatch[1];
        }

        $host = '';
        if (preg_match('/\brelay=([\w.\-\[\]:]+)/i', $raw, $relayMatch)) {
            $host = trim($relayMatch[1], '[]');
        }

        // Sender boş + recipient varsa: bu inbound delivery satırı olabilir
        if ($sender === '') {
            $sender = $recipient;
        }

        return [
            'timestamp' => $this->normaliseTimestamp($ts),
            'sender' => $sender,
            'recipient' => $recipient,
            'subject' => '(log)',
            'status' => $status,
            'size_bytes' => $sizeBytes,
            'smtp_code' => $smtpCode,
            'smtp_message' => $smtpMessage,
            'host' => $host !== '' ? $host : 'localhost',
            'delay_seconds' => (int) round($delay),
        ];
    }

    private function normaliseTimestamp(?string $syslogTs): string
    {
        if (! $syslogTs) {
            return Carbon::now()->toIso8601String();
        }

        try {
            // "May 14 19:18:23" — yıl yok, mevcut yılı yapıştır
            return Carbon::parse($syslogTs)->toIso8601String();
        } catch (\Throwable) {
            return Carbon::now()->toIso8601String();
        }
    }

    private static function domainPart(string $email): string
    {
        $at = strpos($email, '@');
        if ($at === false) {
            return '';
        }

        return substr($email, $at + 1);
    }

    /**
     * @param  array<int, array<string, mixed>>  $entries
     * @return array<int, array<string, mixed>>
     */
    private function applyFilters(array $entries, Request $request): array
    {
        $status = (string) $request->input('status', 'all');
        $sender = (string) $request->input('sender', '');
        $recipient = (string) $request->input('recipient', '');
        $domain = (string) $request->input('domain', '');
        $from = $request->input('from');
        $to = $request->input('to');

        return array_values(array_filter($entries, static function (array $e) use ($status, $sender, $recipient, $domain, $from, $to): bool {
            if ($status !== 'all' && $status !== '' && $e['status'] !== $status) {
                return false;
            }
            if ($sender !== '' && ! str_contains(strtolower((string) $e['sender']), strtolower($sender))) {
                return false;
            }
            if ($recipient !== '' && ! str_contains(strtolower((string) $e['recipient']), strtolower($recipient))) {
                return false;
            }
            if ($domain !== '') {
                $senderDomain = strtolower(self::domainPart((string) $e['sender']));
                if (! str_contains($senderDomain, strtolower($domain))) {
                    return false;
                }
            }
            if ($from !== null && $from !== '') {
                try {
                    if (Carbon::parse($e['timestamp'])->lt(Carbon::parse($from)->startOfDay())) {
                        return false;
                    }
                } catch (\Throwable) {
                }
            }
            if ($to !== null && $to !== '') {
                try {
                    if (Carbon::parse($e['timestamp'])->gt(Carbon::parse($to)->endOfDay())) {
                        return false;
                    }
                } catch (\Throwable) {
                }
            }

            return true;
        }));
    }

    /**
     * @param  array<int, array<string, mixed>>  $filtered
     * @return array<string, int>
     */
    private function buildStats(array $filtered): array
    {
        $stats = ['delivered' => 0, 'sent' => 0, 'deferred' => 0, 'bounced' => 0, 'rejected' => 0, 'total' => count($filtered)];
        foreach ($filtered as $e) {
            if (isset($stats[$e['status']])) {
                $stats[$e['status']]++;
            }
        }

        return $stats;
    }

    /**
     * @param  array<string, mixed>  $entry
     * @return array<int, array<string, string>>
     */
    private function buildTrace(array $entry): array
    {
        $base = Carbon::parse($entry['timestamp']);

        return [
            [
                'time' => $base->toIso8601String(),
                'event' => 'queued',
                'message' => 'Postfix kuyruğa aldı',
                'host' => $entry['host'] ?: 'localhost',
            ],
            [
                'time' => $base->copy()->addSeconds((int) ($entry['delay_seconds'] ?? 1))->toIso8601String(),
                'event' => $entry['status'],
                'message' => trim(($entry['smtp_code'] ?? '') . ' ' . ($entry['smtp_message'] ?? '')),
                'host' => $entry['host'] ?: 'localhost',
            ],
        ];
    }
}
