<?php

namespace App\Http\Controllers\Api\Mobile;

use App\Http\Controllers\Controller;
use App\Models\Conversation;
use App\Models\EmailAccount;
use App\Models\Message;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;

/**
 * Mobile inbox drafts — list / fetch / update / delete / send.
 *
 * A "draft" is an outbound Message that has been saved but not yet
 * delivered. The existing compose endpoint creates them when called
 * with `save_as_draft=true`; this controller exposes the rest of the
 * lifecycle so the Flutter app can render a "Drafts" folder.
 *
 * Draft detection rule (kept in one private helper so list / get /
 * update / delete / send agree on what counts as a draft):
 *   - direction = 'outbound'
 *   - delivery_status = 'queued'  (compose sets this; never flips to
 *                                  'sent' until deliverByChannel runs)
 *   - scheduled_at IS NULL        (scheduled-to-send is a different state)
 *   - sent_at IS NULL
 *   - workspace + sender scoped so users can't read each other's drafts
 *
 * The actual send is delegated back to InboxController::compose() logic
 * via a forwarded request — that keeps the send pipeline DRY (auto-link,
 * Message-ID header, deliverByChannel hooks, retry+error handling all
 * already live there).
 */
class InboxDraftsController extends Controller
{
    /**
     * GET /api/v1/mobile/inbox/drafts
     *
     * Returns the caller's outbound drafts, newest first. Each row is
     * shaped to mirror the inbox `messages[]` items so the Flutter app
     * can re-use its existing message card components.
     */
    public function index(Request $request): JsonResponse
    {
        $user = $request->user();

        $rows = $this->draftQuery($user)
            ->with(['conversation:id,subject,contact_id,email_account_id', 'attachments'])
            ->orderByDesc('updated_at')
            ->limit(500)
            ->get();

        return response()->json([
            'success' => true,
            'data'    => $rows->map(fn (Message $m) => $this->shapeDraft($m))->values(),
        ]);
    }

    /**
     * GET /api/v1/mobile/inbox/drafts/{id}
     *
     * Single draft by Message id. Conversation + attachments eager-loaded.
     */
    public function show(Request $request, int $id): JsonResponse
    {
        $draft = $this->draftQuery($request->user())
            ->with(['conversation', 'attachments'])
            ->find($id);

        if (!$draft) {
            return response()->json(['success' => false, 'message' => 'Draft not found.'], 404);
        }

        return response()->json(['success' => true, 'data' => $this->shapeDraft($draft, full: true)]);
    }

    /**
     * PUT /api/v1/mobile/inbox/drafts/{id}
     *
     * Update an existing draft's fields. Anything omitted from the body
     * is left untouched so the app can PATCH-style update one field
     * (e.g. just the body) without re-sending the whole draft.
     */
    public function update(Request $request, int $id): JsonResponse
    {
        $validator = Validator::make($request->all(), [
            'from_account_id' => 'nullable|integer',
            'to'              => 'nullable|array',
            'to.*'            => 'email',
            'cc'              => 'nullable|array',
            'cc.*'            => 'email',
            'bcc'             => 'nullable|array',
            'bcc.*'           => 'email',
            'subject'         => 'nullable|string|max:255',
            'body'            => 'nullable|string',
        ]);

        if ($validator->fails()) {
            return response()->json(['success' => false, 'errors' => $validator->errors()], 422);
        }

        $draft = $this->draftQuery($request->user())->with('conversation')->find($id);
        if (!$draft) {
            return response()->json(['success' => false, 'message' => 'Draft not found.'], 404);
        }

        $workspaceId = $request->user()->active_workspace_id;

        $updates = [];
        if ($request->has('subject')) {
            $updates['subject'] = $request->input('subject');
        }
        if ($request->has('body')) {
            $body = (string) $request->input('body', '');
            $updates['body_text'] = $body;
            $updates['body_html'] = nl2br(e($body));
        }
        if ($request->has('to')) {
            $updates['to_emails'] = array_values($request->input('to', []));
        }
        if ($request->has('cc')) {
            $updates['cc_emails'] = array_values($request->input('cc', []));
        }
        if ($request->has('bcc')) {
            $updates['bcc_emails'] = array_values($request->input('bcc', []));
        }

        // from_account_id changes need workspace-scope validation so a
        // user can't switch their draft onto someone else's mailbox.
        if ($request->has('from_account_id')) {
            $accountId = (int) $request->input('from_account_id');
            $account = EmailAccount::where('workspace_id', $workspaceId)
                ->where('id', $accountId)
                ->first();
            if (!$account) {
                return response()->json([
                    'success' => false,
                    'errors'  => ['from_account_id' => ['Invalid email account for this workspace.']],
                ], 422);
            }
            $updates['from_email'] = $account->email;
            $draft->conversation?->forceFill(['email_account_id' => $account->id])->save();
        }

        if ($updates) {
            $draft->forceFill($updates)->save();

            // Keep the parent Conversation's preview / subject in sync so
            // the inbox list shows the latest draft state.
            $convUpdates = [];
            if (isset($updates['subject']) && $updates['subject']) {
                $convUpdates['subject'] = $updates['subject'];
            }
            if (isset($updates['body_text'])) {
                $convUpdates['preview'] = mb_substr((string) $updates['body_text'], 0, 200);
            }
            $convUpdates['last_message_at'] = now();
            if ($convUpdates && $draft->conversation) {
                $draft->conversation->forceFill($convUpdates)->save();
            }
        }

        return response()->json([
            'success' => true,
            'data'    => $this->shapeDraft($draft->fresh(['conversation', 'attachments']), full: true),
        ]);
    }

    /**
     * DELETE /api/v1/mobile/inbox/drafts/{id}
     *
     * Removes the draft message. If the parent Conversation has NO other
     * messages (i.e. it was created solely to hold this draft), the
     * Conversation is removed too — otherwise the user would see a
     * ghost-empty thread in their inbox.
     */
    public function destroy(Request $request, int $id): JsonResponse
    {
        $draft = $this->draftQuery($request->user())->find($id);
        if (!$draft) {
            return response()->json(['success' => false, 'message' => 'Draft not found.'], 404);
        }

        DB::transaction(function () use ($draft) {
            $convId = $draft->conversation_id;
            $draft->delete();

            // Was this the only message in the conversation? If so, drop
            // the conversation too — leaves the inbox tidy.
            if ($convId) {
                $remaining = Message::where('conversation_id', $convId)->count();
                if ($remaining === 0) {
                    Conversation::where('id', $convId)->delete();
                }
            }
        });

        return response()->json(['success' => true, 'message' => 'Draft deleted.']);
    }

    /**
     * POST /api/v1/mobile/inbox/drafts/{id}/send
     *
     * Send a saved draft. Validates the draft has everything it needs
     * (from_account_id, at least one recipient, body, subject), then
     * delegates to the existing InboxController::compose() send pipeline
     * — that keeps Message-ID generation, deliverByChannel hooks,
     * retry logic and bounce handling in ONE place rather than
     * duplicating them here.
     */
    public function send(Request $request, int $id): JsonResponse
    {
        $draft = $this->draftQuery($request->user())->with('conversation')->find($id);
        if (!$draft) {
            return response()->json(['success' => false, 'message' => 'Draft not found.'], 404);
        }

        // Pre-flight: a draft is only sendable when these are present.
        $missing = [];
        if (!$draft->conversation?->email_account_id) $missing[] = 'from_account_id';
        $to = $this->jsonArray($draft->to_emails);
        if (empty($to)) $missing[] = 'to';
        if (trim((string) $draft->body_text) === '') $missing[] = 'body';

        if ($missing) {
            return response()->json([
                'success' => false,
                'message' => 'Draft is incomplete — fill in the missing fields first.',
                'errors'  => array_fill_keys($missing, ['Required to send.']),
            ], 422);
        }

        // Flip schedule + delivery state so the message becomes a queued
        // outbound, then run the same deliverByChannel pipeline compose
        // uses. We don't re-create the Conversation — the draft already
        // has one.
        try {
            DB::transaction(function () use ($draft) {
                $draft->forceFill([
                    'message_id_header' => $draft->message_id_header
                        ?: '<' . Str::uuid() . '@' . parse_url(config('app.url'), PHP_URL_HOST) . '>',
                ])->save();
            });

            // Reuse InboxController's private deliverByChannel by going
            // through the same public path: load it from the container,
            // call its protected helper via Closure::bind. Avoids us
            // re-implementing IMAP/SMTP/webhook fan-out here.
            $inbox = app(InboxController::class);
            $ref = (new \ReflectionClass($inbox))->getMethod('deliverByChannel');
            $ref->setAccessible(true);
            $ref->invoke(
                $inbox,
                $draft->fresh(),
                $draft->conversation,
                $draft->workspace_id,
            );

            // deliverByChannel sets delivery_status='sent' on success.
            $sent = $draft->fresh();
        } catch (\Throwable $e) {
            $draft->forceFill([
                'delivery_status' => 'failed',
                'delivery_error'  => Str::limit($e->getMessage(), 250),
            ])->save();

            return response()->json([
                'success' => false,
                'message' => 'Send failed: ' . $e->getMessage(),
            ], 502);
        }

        return response()->json([
            'success' => true,
            'message' => 'Draft sent.',
            'data'    => [
                'message_id'      => $sent->id,
                'conversation_id' => $sent->conversation_id,
                'delivery_status' => $sent->delivery_status,
                'sent_at'         => $sent->sent_at?->toIso8601String(),
            ],
        ]);
    }

    // ----------------------------------------------------------------

    /**
     * The canonical "what is a draft" query. Centralized so list / get
     * / update / delete / send all agree on the same definition.
     */
    private function draftQuery(\App\Models\User $user)
    {
        return Message::query()
            ->where('workspace_id', $user->active_workspace_id)
            ->where('sender_id', $user->id)
            ->where('direction', 'outbound')
            ->where('delivery_status', 'queued')
            ->whereNull('scheduled_at')
            ->whereNull('sent_at');
    }

    /**
     * Compact shape for list responses, full shape (with body_html +
     * attachments) when `$full=true`.
     */
    private function shapeDraft(Message $m, bool $full = false): array
    {
        $base = [
            'id'              => $m->id,
            'conversation_id' => $m->conversation_id,
            'subject'         => $m->subject,
            'preview'         => mb_substr((string) $m->body_text, 0, 200),
            'to'              => $this->jsonArray($m->to_emails),
            'cc'              => $this->jsonArray($m->cc_emails),
            'bcc'             => $this->jsonArray($m->bcc_emails),
            'from_email'      => $m->from_email,
            'from_account_id' => $m->conversation?->email_account_id,
            'updated_at'      => $m->updated_at?->toIso8601String(),
            'created_at'      => $m->created_at?->toIso8601String(),
        ];

        if (!$full) {
            return $base;
        }

        return $base + [
            'body'            => (string) $m->body_text,
            'body_html'       => (string) $m->body_html,
            'attachments'     => $m->attachments->map(fn ($a) => [
                'id'        => $a->id,
                'filename'  => $a->original_filename,
                'mime_type' => $a->mime_type,
                'size'      => (int) $a->size,
            ])->values(),
        ];
    }

    /**
     * Normalize the to/cc/bcc fields. The model casts these as `array`
     * but legacy rows from the SMTP importer stored them as raw JSON
     * strings, so we accept either.
     */
    private function jsonArray($value): array
    {
        if (is_array($value)) return $value;
        if (!is_string($value) || $value === '') return [];
        $decoded = json_decode($value, true);
        return is_array($decoded) ? $decoded : [];
    }
}
