<?php
declare(strict_types=1);

namespace FreshCloud\Mail\MimeParser;

use FreshCloud\Mail\BaseUtils\EmailAddress;
use FreshCloud\Mail\BaseUtils\TextHelper;
use FreshCloud\Mail\BaseUtils\Encoding;
use DateTimeImmutable;

final class SimpleMimeParser implements MimeParserInterface
{
    private const HEADER_PATTERN = '/^([^:]+):\s*(.*)$/m';
    private const BOUNDARY_PATTERN = '/boundary="?([^";]+)"?/i';

    public function parse(string $raw): ParsedMessage
    {
        $raw = trim($raw);
        $headerEnd = strpos($raw, "\r\n\r\n");
        
        $headersStr = substr($raw, 0, $headerEnd);
        $body = substr($raw, $headerEnd + 4);
        
        $headers = $this->parseHeaders($headersStr);
        $subject = $headers['Subject'][0] ?? '';
        $from = EmailAddress::parse($headers['From'][0] ?? '');
        $to = array_map(fn($e) => EmailAddress::parse($e), $headers['To'] ?? []);
        $cc = array_map(fn($e) => EmailAddress::parse($e), $headers['Cc'] ?? []);
        $date = $headers['Date'][0] ?? null ? new DateTimeImmutable($headers['Date'][0]) : null;
        $messageId = $headers['Message-ID'][0] ?? null;
        
        $contentType = $headers['Content-Type'][0] ?? 'text/plain';
        $mimeParts = $this->parseBody($body, $contentType);
        
        $textBody = null;
        $htmlBody = null;
        $attachments = [];
        
        foreach ($mimeParts as $part) {
            $mainType = strtolower(trim(explode(';', $part->contentType, 2)[0]));
            if ($part->disposition === 'attachment' || ($part->filename && !in_array($mainType, ['text/plain', 'text/html']))) {
                $attachments[] = $part;
            } elseif ($mainType === 'text/plain') {
                $textBody = $this->decodeBody($part);
            } elseif ($mainType === 'text/html') {
                $htmlBody = $this->decodeBody($part);
            }
        }
        
        return new ParsedMessage(
            subject: TextHelper::decodeHeader($subject),
            from: $from,
            to: $to,
            cc: $cc,
            date: $date,
            messageId: $messageId,
            textBody: $textBody,
            htmlBody: $htmlBody,
            attachments: $attachments,
            headers: $headers
        );
    }

    private function parseHeaders(string $headersStr): array
    {
        $headers = [];
        preg_match_all(self::HEADER_PATTERN, $headersStr, $matches, PREG_SET_ORDER);
        
        foreach ($matches as $match) {
            $key = trim($match[1]);
            $value = TextHelper::decodeHeader(trim($match[2]));
            $headers[$key][] = $value;
        }
        
        return $headers;
    }

    private function parseBody(string $body, string $contentType): array
    {
        if (stristr($contentType, 'multipart')) {
            preg_match(self::BOUNDARY_PATTERN, $contentType, $matches);
            if (!$matches) {
                return [new MimePart(contentType: $contentType, body: $body)];
            }
            
            $boundary = $matches[1];
            $parts = [];
            $delimiter = "--{$boundary}";
            $segments = explode($delimiter, $body);
            array_shift($segments);
            array_pop($segments);
            
            foreach ($segments as $segment) {
                $segment = trim($segment);
                if ($segment === '--') continue;
                
                $partHeadersEnd = strpos($segment, "\r\n\r\n");
                $partHeaders = substr($segment, 0, $partHeadersEnd);
                $partBody = substr($segment, $partHeadersEnd + 4);
                
                $partHeaders = $this->parseHeaders($partHeaders);
                $partContentType = $partHeaders['Content-Type'][0] ?? 'text/plain';
                $partEncoding = $partHeaders['Content-Transfer-Encoding'][0] ?? '7bit';
                $partDisposition = $partHeaders['Content-Disposition'][0] ?? null;
                
                $filename = null;
                if ($partDisposition) {
                    if (preg_match('/filename="?([^";]+)"?/i', $partDisposition, $fileMatches)) {
                        $filename = $fileMatches[1];
                    }
                }
                
                $parts[] = new MimePart(
                    contentType: $partContentType,
                    charset: $this->extractCharset($partContentType),
                    transferEncoding: $partEncoding,
                    disposition: $partDisposition ? stristr($partDisposition, 'attachment') ? 'attachment' : 'inline' : null,
                    filename: $filename,
                    body: $partBody
                );
            }
            
            return $parts;
        }
        
        return [new MimePart(contentType: $contentType, body: $body)];
    }

    private function extractCharset(string $contentType): string
    {
        if (preg_match('/charset="?([^";]+)"?/i', $contentType, $matches)) {
            return $matches[1];
        }
        return 'utf-8';
    }

    private function decodeBody(MimePart $part): ?string
    {
        $body = $part->body;
        if ($part->transferEncoding === 'quoted-printable') {
            $body = Encoding::quotedPrintableDecode($body);
        } elseif ($part->transferEncoding === 'base64') {
            $body = Encoding::base64Decode($body);
        }
        
        if ($part->charset !== 'utf-8') {
            $body = mb_convert_encoding($body, 'utf-8', $part->charset);
        }
        
        return $body;
    }
}