<?php
declare(strict_types=1);

namespace FreshCloud\Mail\Compose;

use Ramsey\Uuid\Uuid;
use FreshCloud\Mail\Compose\ComposeRequest;
use FreshCloud\Mail\Compose\Draft;
use FreshCloud\Mail\Compose\ComposeResult;
use FreshCloud\Mail\Compose\ComposeStatus;
use FreshCloud\Mail\Log\Logger;
use FreshCloud\Mail\Smtp\SmtpClient;
use FreshCloud\Mail\MailClient\MailClient;
use FreshCloud\Mail\Identity\IdentityProvider;
use FreshCloud\Mail\PocketBaseAdapter\PocketBaseAdapter;

final class Composer
{
    private PocketBaseAdapter $pb;
    private Logger $logger;
    private SmtpClient $smtp;
    private MailClient $mailClient;
    private IdentityProvider $identity;

    public function __construct(
        PocketBaseAdapter $pb,
        Logger $logger,
        SmtpClient $smtp,
        MailClient $mailClient,
        IdentityProvider $identity
    ) {
        $this->pb = $pb;
        $this->logger = $logger;
        $this->smtp = $smtp;
        $this->mailClient = $mailClient;
        $this->identity = $identity;
    }

    public function start(string $userId, string $accountId, string $mode, ?string $replyToMsgId = null, ?string $forwardOfMsgId = null): string
    {
        $validModes = ['new', 'reply', 'reply-all', 'forward'];
        if (!in_array($mode, $validModes, true)) {
            throw new \InvalidArgumentException("Invalid compose mode: $mode");
        }

        $draft = new Draft();
        $draft->userId = $userId;
        $draft->accountId = $accountId;

        if ($mode === 'new') {
            $draft->to = [];
            $draft->cc = [];
            $draft->bcc = [];
            $draft->subject = '';
            $draft->bodyHtml = '';
            $draft->bodyText = '';
        } else {
            $originalMsgId = $mode === 'forward' ? $forwardOfMsgId : $replyToMsgId;
            $original = $this->mailClient->getMessage($userId, $accountId, (string) $originalMsgId);

            $draft->subject = ($mode === 'forward' ? 'Fwd: ' : 'Re: ') . ($original['subject'] ?? '');
            $draft->bodyHtml = $this->quoteMessage((string) ($original['bodyHtml'] ?? $original['bodyText'] ?? ''), (string) ($original['from'] ?? ''), (string) ($original['subject'] ?? ''));

            if ($mode === 'reply' || $mode === 'reply-all') {
                $draft->to = [(string) ($original['from'] ?? '')];
                if ($mode === 'reply-all' && !empty($original['cc']) && is_array($original['cc'])) {
                    $self = $this->identity->getFromAddress($userId, $accountId);
                    $draft->cc = array_values(array_diff($original['cc'], [$self]));
                }
            }
        }

        $draftId = Uuid::uuid4()->toString();
        $draft->id = $draftId;

        $this->pb->collection('email_drafts')->create($draft->toArray());

        $this->logger->log('compose.started', [
            'user_id' => $userId,
            'account_id' => $accountId,
            'mode' => $mode,
            'draft_id' => $draftId
        ]);

        return $draftId;
    }

    public function saveDraft(string $draftId, ComposeRequest $request, string $userId): void
    {
        $response = $this->pb->collection('email_drafts')->getList(1, 1, [
            'filter' => 'id="' . $draftId . '"'  ]);

        if (empty($response['items'])) {
            throw new \RuntimeException("Draft not found: $draftId");
        }

        $draft = Draft::fromArray((array) $response['items'][0]);
        if ($draft->userId !== $userId) {
            throw new \RuntimeException("Unauthorized access to draft");
        }

        $draft->to = $request->to;
        $draft->cc = $request->cc;
        $draft->bcc = $request->bcc;
        $draft->subject = $request->subject;
        $draft->bodyHtml = $request->bodyHtml;
        $draft->bodyText = $request->bodyText;
        $draft->attachmentIds = $request->attachmentIds;
        $draft->updatedAt = new \DateTimeImmutable();

        $this->pb->collection('email_drafts')->update($draftId, $draft->toArray());

        $this->logger->log('compose.draft_saved', [
            'draft_id' => $draftId,
            'user_id' => $userId,
            'fields_count' => count($request->toArray())
        ]);
    }

    public function resumeDraft(string $draftId, string $userId): ComposeRequest
    {
        $response = $this->pb->collection('email_drafts')->getList(1, 1, [
            'filter' => 'id="' . $draftId . '"'  ]);

        if (empty($response['items'])) {
            throw new \RuntimeException("Draft not found: $draftId");
        }

        $draft = Draft::fromArray((array) $response['items'][0]);
        if ($draft->userId !== $userId) {
            throw new \RuntimeException("Unauthorized access to draft");
        }
        return $draft->toRequest();
    }

    private function quoteMessage(string $original, string $from, string $subject): string
    {
        $date = date('Y-m-d H:i');
        $quoted = '';
        foreach (explode("\n", $original) as $line) {
            $quoted .= '> ' . $line . "\n";
        }
        return '<p><br></p>'
            . '<p>On ' . $date . ', ' . htmlspecialchars($from, ENT_QUOTES) . ' wrote:</p>'
            . '<blockquote>' . nl2br(htmlspecialchars($quoted, ENT_QUOTES)) . '</blockquote>';
    }

    public function deleteDraft(string $draftId, string $userId): void
    {
        $draft = $this->pb->collection('email_drafts')->getOne($draftId);
        if (!$draft) {
            throw new \RuntimeException("Draft not found: $draftId");
        }
        $existing = Draft::fromArray((array) $draft);
        if ($existing->userId !== $userId) {
            throw new \RuntimeException("Unauthorized access to draft");
        }

        $this->pb->collection('email_drafts')->delete($draftId);
        $this->logger->log('compose.draft_deleted', ['draft_id' => $draftId, 'user_id' => $userId]);
    }

    public function send(string $userId, string $accountId, ComposeRequest $request): ComposeResult
    {
        try {
            if ($request->draftId !== null) {
                $row = $this->pb->collection('email_drafts')->getOne($request->draftId);
                if (!$row) {
                    throw new \RuntimeException("Draft not found: {$request->draftId}");
                }
                $existing = Draft::fromArray((array) $row);
                $request = new ComposeRequest(
                    mode: $request->mode,
                    to: $request->to ?: $existing->to,
                    cc: $request->cc ?: $existing->cc,
                    bcc: $request->bcc ?: $existing->bcc,
                    subject: $request->subject !== '' ? $request->subject : $existing->subject,
                    bodyHtml: $request->bodyHtml !== '' ? $request->bodyHtml : $existing->bodyHtml,
                    bodyText: $request->bodyText !== '' ? $request->bodyText : $existing->bodyText,
                    attachmentIds: $request->attachmentIds ?: $existing->attachmentIds,
                    accountId: $request->accountId !== '' ? $request->accountId : $accountId,
                    draftId: $request->draftId,
                    saveAsDraft: $request->saveAsDraft,
                    replyToMsgId: $request->replyToMsgId,
                    forwardOfMsgId: $request->forwardOfMsgId,
                );
            }

            foreach ([$request->to, $request->cc, $request->bcc] as $recipients) {
                foreach ($recipients as $email) {
                    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
                        throw new \InvalidArgumentException("Invalid email address: $email");
                    }
                }
            }
            if (trim($request->subject) === '') {
                throw new \InvalidArgumentException('Subject is required');
            }
            if (trim($request->bodyHtml) === '' && trim($request->bodyText) === '') {
                throw new \InvalidArgumentException('Body is required');
            }
            if (trim($accountId) === '') {
                throw new \InvalidArgumentException('Account ID is required');
            }

            $from = $this->identity->getFromAddress($userId, $accountId);
            if (empty($from)) {
                throw new \RuntimeException('No From identity for account');
            }

            $attachmentPaths = [];
            $messageId = $this->smtp->send(
                $from,
                $request->to,
                $request->subject,
                $request->bodyHtml,
                $request->bodyText,
                $attachmentPaths
            );

            if ($request->draftId !== null) {
                $this->pb->collection('email_drafts')->update($request->draftId, [
                    'status' => 'sent',
                    'last_error' => null,
                    'updated_at' => date('c'),
                ]);
            }

            $this->logger->log('compose.sent', [
                'draft_id' => $request->draftId,
                'message_id' => $messageId,
                'user_id' => $userId,
            ]);

            return ComposeResult::fromArray([
                'status' => 'sent',
                'message_id' => $messageId,
                'draft_id' => $request->draftId,
                'sent_at' => date('c'),
            ]);
        } catch (\Throwable $e) {
            if ($request->draftId !== null) {
                try {
                    $this->pb->collection('email_drafts')->update($request->draftId, [
                        'status' => 'failed',
                        'last_error' => $e->getMessage(),
                        'updated_at' => date('c'),
                    ]);
                } catch (\Throwable $ignored) {}
            }

            $this->logger->log('compose.failed', [
                'draft_id' => $request->draftId,
                'user_id' => $userId,
                'error' => $e->getMessage(),
            ]);

            return ComposeResult::fromArray([
                'status' => 'failed',
                'draft_id' => $request->draftId,
                'error' => $e->getMessage(),
            ]);
        }
    }
}
