<?php

require_once __DIR__ . '/../../core/database.php';
require_once __DIR__ . '/../../core/config.php';

class BillingDocumentEmailService
{
    private $pdo;
    private $appRoot;

    public function __construct(PDO $pdo = null)
    {
        $this->pdo = $pdo ?: db();
        $this->appRoot = realpath(dirname(__DIR__, 2));
    }

    public function sendQuoteDocument($quoteId, array $input)
    {
        $quoteId = (int)$quoteId;
        if ($quoteId <= 0) {
            throw new InvalidArgumentException('Cotizacion no valida.');
        }

        $quote = $this->loadQuote($quoteId);
        if (!$quote) {
            throw new RuntimeException('Cotizacion no encontrada.');
        }

        $logId = isset($input['log_id']) ? (int)$input['log_id'] : 0;
        $issue = $this->loadIssueLog($quoteId, $logId);
        if (!$issue) {
            throw new RuntimeException('No se encontro documento tributario emitido para enviar.');
        }

        $to = trim((string)($input['recipient_email'] ?? ''));
        if ($to === '') {
            $to = trim((string)($quote['email'] ?? ''));
        }
        if (!$this->isValidEmail($to)) {
            throw new InvalidArgumentException('El email destinatario no es valido.');
        }

        $cc = $this->parseEmails((string)($input['cc_email'] ?? ''));
        $subject = trim((string)($input['subject'] ?? ''));
        if ($subject === '') {
            $subject = 'Documento tributario ICYLAB ' . ($issue['document_number'] ?: $quote['quote_number']);
        }

        $body = trim((string)($input['body'] ?? ''));
        if ($body === '') {
            $body = $this->defaultBody($quote, $issue);
        }

        $attachPdf = !empty($input['attach_pdf']);
        $attachXml = !empty($input['attach_xml']);
        $attachments = array();
        $pdfPathForLog = null;
        $xmlPathForLog = null;

        if ($attachPdf) {
            if (empty($issue['pdf_file_path'])) {
                throw new RuntimeException('No hay PDF disponible para adjuntar.');
            }
            $pdfFile = $this->resolveStorageFile($issue['pdf_file_path']);
            $attachments[] = array(
                'path' => $pdfFile,
                'name' => 'Factura_' . $this->safeFilePart($issue['document_number'] ?: $quote['quote_number']) . '.pdf',
                'mime' => 'application/pdf'
            );
            $pdfPathForLog = $issue['pdf_file_path'];
        }

        if ($attachXml) {
            if (empty($issue['xml_file_path'])) {
                throw new RuntimeException('No hay XML disponible para adjuntar.');
            }
            $xmlFile = $this->resolveStorageFile($issue['xml_file_path']);
            $attachments[] = array(
                'path' => $xmlFile,
                'name' => 'Factura_' . $this->safeFilePart($issue['document_number'] ?: $quote['quote_number']) . '.xml',
                'mime' => 'application/xml'
            );
            $xmlPathForLog = $issue['xml_file_path'];
        }

        if (empty($attachments)) {
            throw new RuntimeException('Selecciona al menos PDF o XML para enviar.');
        }

        $emailId = $this->insertEmailLog($issue, $quote, $to, $cc, $subject, $body, $pdfPathForLog, $xmlPathForLog);

        try {
            $this->sendEmail($to, $cc, $subject, $body, $attachments);
            $this->markSent($emailId);
            return array(
                'ok' => true,
                'email_id' => (int)$emailId,
                'recipient_email' => $to,
                'cc_email' => implode(', ', $cc),
                'subject' => $subject,
                'attachments' => array(
                    'pdf' => $attachPdf,
                    'xml' => $attachXml
                ),
                'message' => 'Documento enviado correctamente.'
            );
        } catch (Throwable $e) {
            $this->markError($emailId, $e->getMessage());
            throw $e;
        }
    }

    private function loadQuote($quoteId)
    {
        $stmt = $this->pdo->prepare("\n            SELECT q.id, q.quote_number, q.total, q.invoice_number, q.facto_document_id,\n                   c.customer_name, c.commercial_name, c.legal_name, c.email, c.customer_tax_id\n            FROM crm_quotes q\n            JOIN crm_customers c ON c.id = q.customer_id\n            WHERE q.id = ?\n            LIMIT 1\n        ");
        $stmt->execute(array($quoteId));
        return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
    }

    private function loadIssueLog($quoteId, $logId)
    {
        if ($logId > 0) {
            $stmt = $this->pdo->prepare("\n                SELECT *\n                FROM billing_issue_logs\n                WHERE id = ?\n                  AND quote_id = ?\n                  AND mode = 'issue'\n                  AND status IN ('success','sent','pending_reconcile')\n                LIMIT 1\n            ");
            $stmt->execute(array($logId, $quoteId));
            return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
        }

        $stmt = $this->pdo->prepare("\n            SELECT *\n            FROM billing_issue_logs\n            WHERE quote_id = ?\n              AND mode = 'issue'\n              AND status IN ('success','sent','pending_reconcile')\n            ORDER BY id DESC\n            LIMIT 1\n        ");
        $stmt->execute(array($quoteId));
        return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
    }

    private function insertEmailLog(array $issue, array $quote, $to, array $cc, $subject, $body, $pdfPath, $xmlPath)
    {
        $userId = function_exists('current_user_id') ? (int)current_user_id() : 0;
        $stmt = $this->pdo->prepare("\n            INSERT INTO billing_document_emails\n            (environment, quote_id, facto_document_id, recipient_email, cc_email, subject, body, attachment_pdf_path, attachment_xml_path, status, sent_by)\n            VALUES\n            (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?)\n        ");
        $stmt->execute(array(
            $issue['environment'] ?: 'sandbox',
            (int)$quote['id'],
            (string)($issue['facto_document_id'] ?: $quote['facto_document_id'] ?: ''),
            $to,
            implode(', ', $cc),
            $subject,
            $body,
            $pdfPath,
            $xmlPath,
            $userId ?: null
        ));
        return (int)$this->pdo->lastInsertId();
    }

    private function markSent($emailId)
    {
        $stmt = $this->pdo->prepare("UPDATE billing_document_emails SET status = 'sent', sent_at = NOW(), error_message = NULL WHERE id = ?");
        $stmt->execute(array((int)$emailId));
    }

    private function markError($emailId, $message)
    {
        $stmt = $this->pdo->prepare("UPDATE billing_document_emails SET status = 'error', error_message = ? WHERE id = ?");
        $stmt->execute(array(substr((string)$message, 0, 2000), (int)$emailId));
    }

    private function defaultBody(array $quote, array $issue)
    {
        $customerName = $quote['legal_name'] ?: ($quote['commercial_name'] ?: $quote['customer_name']);
        $folio = $issue['document_number'] ?: $quote['invoice_number'];
        return '<p>Hola,</p>' .
            '<p>Adjuntamos el documento tributario asociado a la cotizacion <strong>' . $this->esc($quote['quote_number']) . '</strong>.</p>' .
            '<p><strong>Cliente:</strong> ' . $this->esc($customerName) . '<br>' .
            '<strong>Folio:</strong> ' . $this->esc($folio ?: 'No informado') . '</p>' .
            '<p>Saludos,<br>ICYLAB</p>';
    }

    private function sendEmail($to, array $cc, $subject, $htmlBody, array $attachments)
    {
        $textBody = $this->htmlToText($htmlBody);
        if (defined('SMTP_HOST') && trim((string)SMTP_HOST) !== '') {
            $this->sendSmtp($to, $cc, $subject, $htmlBody, $textBody, $attachments);
            return;
        }
        $this->sendMailFunction($to, $cc, $subject, $htmlBody, $textBody, $attachments);
    }

    private function sendMailFunction($to, array $cc, $subject, $htmlBody, $textBody, array $attachments)
    {
        $fromEmail = defined('NOTIFICATIONS_EMAIL_FROM') && NOTIFICATIONS_EMAIL_FROM !== '' ? NOTIFICATIONS_EMAIL_FROM : 'no-reply@app.icylab.cl';
        $fromName = defined('NOTIFICATIONS_EMAIL_FROM_NAME') && NOTIFICATIONS_EMAIL_FROM_NAME !== '' ? NOTIFICATIONS_EMAIL_FROM_NAME : 'ICYLAB OS';
        $message = $this->buildMimeMessage($to, $cc, $subject, $htmlBody, $textBody, $fromEmail, $fromName, $attachments, false);

        $headers = array();
        foreach (explode("\r\n", substr($message, 0, strpos($message, "\r\n\r\n"))) as $headerLine) {
            if (stripos($headerLine, 'To:') === 0 || stripos($headerLine, 'Subject:') === 0) {
                continue;
            }
            $headers[] = $headerLine;
        }
        $body = substr($message, strpos($message, "\r\n\r\n") + 4);
        $ok = mail($to, $this->encodeHeader($subject), $body, implode("\r\n", $headers));
        if (!$ok) {
            throw new RuntimeException('mail() no pudo aceptar el mensaje.');
        }
    }

    private function sendSmtp($to, array $cc, $subject, $htmlBody, $textBody, array $attachments)
    {
        $host = trim((string)SMTP_HOST);
        $port = defined('SMTP_PORT') && SMTP_PORT ? (int)SMTP_PORT : 587;
        $secure = defined('SMTP_SECURE') ? strtolower(trim((string)SMTP_SECURE)) : 'tls';
        $timeout = 30;
        $transportHost = ($secure === 'ssl' || $secure === 'smtps' || $port === 465) ? 'ssl://' . $host : $host;
        $errno = 0;
        $errstr = '';
        $socket = stream_socket_client($transportHost . ':' . $port, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT);
        if (!$socket) {
            throw new RuntimeException('No fue posible conectar al SMTP: ' . $errstr);
        }
        stream_set_timeout($socket, $timeout);

        try {
            $this->smtpExpect($socket, array(220));
            $ehloName = defined('SMTP_EHLO_DOMAIN') && SMTP_EHLO_DOMAIN !== '' ? SMTP_EHLO_DOMAIN : 'app.icylab.cl';
            $this->smtpCommand($socket, 'EHLO ' . $ehloName, array(250));
            if ($secure === 'tls' || $secure === 'starttls') {
                $this->smtpCommand($socket, 'STARTTLS', array(220));
                if (!stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
                    throw new RuntimeException('No fue posible iniciar TLS con el servidor SMTP.');
                }
                $this->smtpCommand($socket, 'EHLO ' . $ehloName, array(250));
            }
            $username = defined('SMTP_USERNAME') ? trim((string)SMTP_USERNAME) : '';
            $password = defined('SMTP_PASSWORD') ? (string)SMTP_PASSWORD : '';
            if ($username !== '') {
                $this->smtpCommand($socket, 'AUTH LOGIN', array(334));
                $this->smtpCommand($socket, base64_encode($username), array(334));
                $this->smtpCommand($socket, base64_encode($password), array(235));
            }
            $fromEmail = defined('NOTIFICATIONS_EMAIL_FROM') && NOTIFICATIONS_EMAIL_FROM !== '' ? NOTIFICATIONS_EMAIL_FROM : $username;
            $fromName = defined('NOTIFICATIONS_EMAIL_FROM_NAME') && NOTIFICATIONS_EMAIL_FROM_NAME !== '' ? NOTIFICATIONS_EMAIL_FROM_NAME : 'ICYLAB OS';
            if ($fromEmail === '') {
                throw new RuntimeException('Falta configurar NOTIFICATIONS_EMAIL_FROM o SMTP_USERNAME.');
            }
            $message = $this->buildMimeMessage($to, $cc, $subject, $htmlBody, $textBody, $fromEmail, $fromName, $attachments, true);
            $this->smtpCommand($socket, 'MAIL FROM:<' . $fromEmail . '>', array(250));
            $allRecipients = array_merge(array($to), $cc);
            foreach ($allRecipients as $recipient) {
                $this->smtpCommand($socket, 'RCPT TO:<' . $recipient . '>', array(250, 251));
            }
            $this->smtpCommand($socket, 'DATA', array(354));
            fwrite($socket, $message . "\r\n.\r\n");
            $this->smtpExpect($socket, array(250));
            $this->smtpCommand($socket, 'QUIT', array(221));
        } finally {
            fclose($socket);
        }
    }

    private function buildMimeMessage($to, array $cc, $subject, $htmlBody, $textBody, $fromEmail, $fromName, array $attachments, $includeEnvelopeHeaders)
    {
        $mixedBoundary = 'mx_' . bin2hex(random_bytes(12));
        $altBoundary = 'alt_' . bin2hex(random_bytes(12));
        $headers = array();
        if ($includeEnvelopeHeaders) {
            $headers[] = 'Date: ' . date(DATE_RFC2822);
            $headers[] = 'To: <' . $to . '>';
            if (!empty($cc)) {
                $headers[] = 'Cc: ' . implode(', ', array_map(function ($email) { return '<' . $email . '>'; }, $cc));
            }
            $headers[] = 'From: ' . $this->encodeHeader($fromName) . ' <' . $fromEmail . '>';
            $headers[] = 'Subject: ' . $this->encodeHeader($subject);
        } else {
            $headers[] = 'MIME-Version: 1.0';
            $headers[] = 'From: ' . $this->encodeHeader($fromName) . ' <' . $fromEmail . '>';
            if (!empty($cc)) {
                $headers[] = 'Cc: ' . implode(', ', $cc);
            }
        }
        $headers[] = 'MIME-Version: 1.0';
        $headers[] = 'Content-Type: multipart/mixed; boundary="' . $mixedBoundary . '"';

        $body = implode("\r\n", $headers) . "\r\n\r\n";
        $body .= '--' . $mixedBoundary . "\r\n";
        $body .= 'Content-Type: multipart/alternative; boundary="' . $altBoundary . "\"\r\n\r\n";
        $body .= '--' . $altBoundary . "\r\n";
        $body .= "Content-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: 8bit\r\n\r\n";
        $body .= $this->dotEscape($textBody) . "\r\n\r\n";
        $body .= '--' . $altBoundary . "\r\n";
        $body .= "Content-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: 8bit\r\n\r\n";
        $body .= $this->dotEscape($htmlBody) . "\r\n\r\n";
        $body .= '--' . $altBoundary . "--\r\n";

        foreach ($attachments as $att) {
            $content = file_get_contents($att['path']);
            if ($content === false) {
                throw new RuntimeException('No fue posible leer adjunto: ' . $att['name']);
            }
            $body .= '--' . $mixedBoundary . "\r\n";
            $body .= 'Content-Type: ' . $att['mime'] . '; name="' . $this->mimeFilename($att['name']) . "\"\r\n";
            $body .= "Content-Transfer-Encoding: base64\r\n";
            $body .= 'Content-Disposition: attachment; filename="' . $this->mimeFilename($att['name']) . "\"\r\n\r\n";
            $body .= chunk_split(base64_encode($content)) . "\r\n";
        }
        $body .= '--' . $mixedBoundary . '--';
        return $body;
    }

    private function resolveStorageFile($relativePath)
    {
        if (!$this->appRoot) {
            throw new RuntimeException('No fue posible resolver APP root.');
        }
        $relative = ltrim(str_replace(array('..', '\\'), array('', '/'), (string)$relativePath), '/');
        $file = realpath($this->appRoot . '/' . $relative);
        $storageRoot = realpath($this->appRoot . '/storage/billing');
        if (!$file || !$storageRoot || strpos($file, $storageRoot) !== 0 || !is_file($file)) {
            throw new RuntimeException('Archivo adjunto no encontrado o fuera de storage autorizado.');
        }
        return $file;
    }

    private function parseEmails($raw)
    {
        $raw = trim((string)$raw);
        if ($raw === '') return array();
        $parts = preg_split('/[,;\s]+/', $raw);
        $emails = array();
        foreach ($parts as $part) {
            $email = trim($part);
            if ($email === '') continue;
            if (!$this->isValidEmail($email)) {
                throw new InvalidArgumentException('Email CC invalido: ' . $email);
            }
            $emails[] = $email;
        }
        return array_values(array_unique($emails));
    }

    private function isValidEmail($email)
    {
        return (bool)filter_var($email, FILTER_VALIDATE_EMAIL);
    }

    private function htmlToText($html)
    {
        $html = preg_replace('/<br\s*\/?>/i', "\n", $html);
        $html = preg_replace('/<\/p>/i', "\n\n", $html);
        return trim(html_entity_decode(strip_tags($html), ENT_QUOTES, 'UTF-8'));
    }

    private function encodeHeader($value)
    {
        return '=?UTF-8?B?' . base64_encode((string)$value) . '?=';
    }

    private function dotEscape($body)
    {
        return preg_replace('/^\./m', '..', (string)$body);
    }

    private function smtpCommand($socket, $command, array $expectedCodes)
    {
        fwrite($socket, $command . "\r\n");
        return $this->smtpExpect($socket, $expectedCodes);
    }

    private function smtpExpect($socket, array $expectedCodes)
    {
        $response = '';
        do {
            $line = fgets($socket, 515);
            if ($line === false) {
                throw new RuntimeException('Respuesta SMTP vacia o conexion cerrada.');
            }
            $response .= $line;
            $code = (int)substr($line, 0, 3);
            $more = isset($line[3]) && $line[3] === '-';
        } while ($more);
        if (!in_array($code, $expectedCodes, true)) {
            throw new RuntimeException('Respuesta SMTP inesperada: ' . trim($response));
        }
        return $response;
    }

    private function mimeFilename($name)
    {
        return str_replace(array("\r", "\n", '"'), array('', '', ''), (string)$name);
    }

    private function safeFilePart($value)
    {
        $value = preg_replace('/[^A-Za-z0-9_-]+/', '_', (string)$value);
        return trim($value, '_') ?: 'documento';
    }

    private function esc($value)
    {
        return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
    }
}
