<?php
namespace App\Services;

use App\Core\Config;
use App\Core\Database;

/**
 * Email-to-ticket intake (FR-SUP-002, FR-SUP-003, FR-SUP-006). Polled from
 * the cron/email_ingest.php script — see NFR-03 (ticket created within 2
 * minutes of arrival). Requires the php-imap extension; on shared cPanel
 * hosting this is toggled under "Select PHP Version" > Extensions.
 */
class EmailIngestService
{
    public static function poll(): array
    {
        $summary = ['created' => 0, 'appended' => 0, 'quarantined' => 0, 'skipped' => 0];

        if (!Config::get('imap.enabled')) {
            return $summary + ['note' => 'IMAP polling is disabled in config.php (imap.enabled = false).'];
        }
        if (!extension_loaded('imap')) {
            return $summary + ['note' => 'The php-imap extension is not enabled on this server.'];
        }

        $mailbox = Config::get('imap.mailbox');
        $user = Config::get('imap.username');
        $pass = Config::get('imap.password');

        $conn = @imap_open($mailbox, $user, $pass);
        if (!$conn) {
            return $summary + ['note' => 'IMAP connection failed: ' . imap_last_error()];
        }

        $emails = imap_search($conn, 'UNSEEN') ?: [];

        foreach ($emails as $msgNo) {
            self::processMessage($conn, $msgNo, $summary);
        }

        imap_close($conn);
        return $summary;
    }

    private static function processMessage($conn, int $msgNo, array &$summary): void
    {
        $header = imap_headerinfo($conn, $msgNo);
        $fromAddress = strtolower($header->from[0]->mailbox . '@' . $header->from[0]->host);
        $subject = imap_utf8($header->subject ?? '(no subject)');
        $body = self::extractPlainTextBody($conn, $msgNo);

        // FR-SUP-003: match an existing ticket number in the subject or references header.
        if (preg_match('/TKT-\d{4}-\d{5}/', $subject, $m)) {
            $ticket = Database::one('SELECT * FROM tickets WHERE ticket_no = :no', ['no' => $m[0]]);
            if ($ticket) {
                $contact = Database::one('SELECT * FROM contacts WHERE email = :e', ['e' => $fromAddress]);
                TicketService::addMessage($ticket, 'inbound_email', $body, null, $contact['id'] ?? null);
                imap_setflag_full($conn, (string)$msgNo, '\\Seen');
                $summary['appended']++;
                return;
            }
        }

        // FR-SUP-006: reject mail from a sender who is not a registered contact of an active customer.
        $contact = Database::one(
            "SELECT c.*, cu.is_active AS customer_active FROM contacts c
             JOIN customers cu ON cu.id = c.customer_id
             WHERE c.email = :e AND c.is_active = 1", ['e' => $fromAddress]
        );
        if (!$contact || !$contact['customer_active']) {
            Database::insert('mail_quarantine', [
                'from_address' => $fromAddress, 'subject' => $subject, 'body' => $body,
                'reason' => 'Sender is not a registered contact of an active customer',
            ]);
            imap_setflag_full($conn, (string)$msgNo, '\\Seen');
            $summary['quarantined']++;
            return;
        }

        $defaultProduct = Database::one('SELECT id FROM products WHERE is_active = 1 ORDER BY id LIMIT 1');
        $defaultCategory = Database::one("SELECT id FROM categories WHERE name = 'Functional query' LIMIT 1")
            ?? Database::one('SELECT id FROM categories WHERE parent_id IS NULL ORDER BY id LIMIT 1');
        $defaultPriority = Database::one("SELECT id FROM priorities WHERE code = 'P3' LIMIT 1");

        if (!$defaultProduct || !$defaultCategory || !$defaultPriority) {
            $summary['skipped']++;
            return;
        }

        $ticket = TicketService::create([
            'contact_id' => $contact['id'],
            'product_id' => $defaultProduct['id'],
            'subject' => $subject !== '' ? $subject : '(No subject)',
            'description' => $body !== '' ? $body : '(Empty message body)',
            'category_id' => $defaultCategory['id'],
            'priority_id' => $defaultPriority['id'],
            'severity' => 'Minor impact',
            'channel' => 'Email',
        ], null, null);

        self::storeAttachments($conn, $msgNo, (int)$ticket['id']);
        imap_setflag_full($conn, (string)$msgNo, '\\Seen');
        $summary['created']++;
    }

    private static function extractPlainTextBody($conn, int $msgNo): string
    {
        $structure = imap_fetchstructure($conn, $msgNo);
        if (!empty($structure->parts)) {
            foreach ($structure->parts as $i => $part) {
                if ($part->subtype === 'PLAIN') {
                    $text = imap_fetchbody($conn, $msgNo, (string)($i + 1));
                    return self::decodePart($text, $part);
                }
            }
            // No plain part found: fall back to the first part.
            $text = imap_fetchbody($conn, $msgNo, '1');
            return trim(strip_tags($text));
        }
        $text = imap_body($conn, $msgNo);
        return trim(strip_tags($text));
    }

    private static function decodePart(string $text, $part): string
    {
        if ($part->encoding === 3) $text = base64_decode($text);
        elseif ($part->encoding === 4) $text = quoted_printable_decode($text);
        return trim($text);
    }

    private static function storeAttachments($conn, int $msgNo, int $ticketId): void
    {
        $structure = imap_fetchstructure($conn, $msgNo);
        if (empty($structure->parts)) return;

        foreach ($structure->parts as $i => $part) {
            $isAttachment = false;
            $filename = null;
            foreach (($part->dparameters ?? []) as $p) {
                if (strtolower($p->attribute) === 'filename') { $isAttachment = true; $filename = $p->value; }
            }
            foreach (($part->parameters ?? []) as $p) {
                if (strtolower($p->attribute) === 'name') { $isAttachment = true; $filename = $filename ?? $p->value; }
            }
            if (!$isAttachment || !$filename) continue;

            $data = imap_fetchbody($conn, $msgNo, (string)($i + 1));
            if ($part->encoding === 3) $data = base64_decode($data);
            elseif ($part->encoding === 4) $data = quoted_printable_decode($data);

            $tmp = tempnam(sys_get_temp_dir(), 'stsmail');
            file_put_contents($tmp, $data);
            try {
                $meta = AttachmentService::store([
                    'name' => $filename, 'type' => null, 'tmp_name' => $tmp,
                    'error' => UPLOAD_ERR_OK, 'size' => filesize($tmp),
                ], $ticketId);
                Database::insert('ticket_attachments', array_merge($meta, ['ticket_id' => $ticketId]));
            } catch (\Throwable $e) {
                error_log('Email attachment skipped: ' . $e->getMessage());
            } finally {
                @unlink($tmp);
            }
        }
    }
}
