<?php

namespace App\Http\Controllers\Api\Mobile;

use App\Http\Controllers\Controller;
use App\Models\Attachment;
use App\Models\Campaign;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;

/**
 * Mobile campaign attachments — upload, list, delete files that should
 * be sent with the campaign (PDFs, images, ZIPs, …).
 *
 * The Flutter composer uploads attachments while the user is still
 * editing the draft (before the campaign even has an audience). Each
 * upload returns an Attachment row keyed by `campaign_id`; the IDs
 * are then either implicit (already linked to this campaign) or can
 * be passed into POST /campaigns + PUT /campaigns to copy attachments
 * from another draft.
 *
 * At send time, SendCampaignEmailJob iterates the campaign's
 * attachments relation and forwards each file via the Symfony Mailer
 * attach() API.
 *
 * Endpoints (all mounted under /api/v1/mobile/campaigns/{id}):
 *   GET    /attachments              list
 *   POST   /attachments              upload one (multipart/form-data, field: file)
 *   DELETE /attachments/{attachmentId}  remove
 */
class CampaignAttachmentsController extends Controller
{
    /** Hard cap to prevent runaway uploads. Matches inbox compose limit. */
    private const MAX_BYTES = 25 * 1024 * 1024; // 25 MB

    /** Disk used for attachment storage. Aligns with inbox attachments. */
    private const DISK = 'local';

    public function index(Request $request, int $id): JsonResponse
    {
        $campaign = $this->resolveCampaign($request, $id);
        if (!$campaign instanceof Campaign) return $campaign;

        $rows = $campaign->attachments()->orderBy('id', 'desc')->get();
        return response()->json([
            'data' => $rows->map(fn (Attachment $a) => $this->shape($a))->all(),
        ]);
    }

    public function store(Request $request, int $id): JsonResponse
    {
        $campaign = $this->resolveCampaign($request, $id);
        if (!$campaign instanceof Campaign) return $campaign;

        // Refuse to attach to a campaign that's already sending — the
        // recipient jobs are dispatched and would not pick the new file
        // up consistently.
        if (in_array($campaign->status, ['sending', 'sent', 'paused'], true)) {
            return response()->json([
                'message' => "Cannot add attachments to a campaign with status '{$campaign->status}'.",
            ], 422);
        }

        Validator::make($request->all(), [
            'file' => ['required', 'file', 'max:' . (self::MAX_BYTES / 1024)],
            // Optional flag for inline images referenced via cid: in the body.
            // Inline attachments are sent BUT not surfaced as visible
            // attachments on the recipient's mail client.
            'is_inline'  => ['nullable', 'boolean'],
            'content_id' => ['nullable', 'string', 'max:255'],
        ])->validate();

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

        // Store under workspace-scoped folder so a leaked direct URL can
        // only ever resolve files belonging to that workspace.
        $folder = "campaign-attachments/{$workspaceId}/{$campaign->id}";
        $filename = Str::uuid()->toString() . '.' . strtolower($file->getClientOriginalExtension() ?: 'bin');
        $path = $file->storeAs($folder, $filename, self::DISK);

        $attachment = Attachment::create([
            'campaign_id'       => $campaign->id,
            'message_id'        => null,
            'workspace_id'      => $workspaceId,
            'filename'          => $filename,
            'original_filename' => $file->getClientOriginalName(),
            'mime_type'         => $file->getMimeType() ?: 'application/octet-stream',
            'size'              => $file->getSize(),
            'storage_path'      => $path,
            'is_inline'         => (bool) $request->boolean('is_inline'),
            'content_id'        => $request->input('content_id'),
        ]);

        return response()->json([
            'message' => __('Attachment uploaded.'),
            'data'    => $this->shape($attachment),
        ], 201);
    }

    public function destroy(Request $request, int $id, int $attachmentId): JsonResponse
    {
        $campaign = $this->resolveCampaign($request, $id);
        if (!$campaign instanceof Campaign) return $campaign;

        if (in_array($campaign->status, ['sending'], true)) {
            return response()->json([
                'message' => 'Cannot remove attachments from a campaign that is currently sending.',
            ], 422);
        }

        $attachment = $campaign->attachments()->where('id', $attachmentId)->first();
        if (!$attachment) {
            return response()->json(['message' => 'Attachment not found.'], 404);
        }

        // Remove the file from disk first; the DB row is the system of
        // record so we'd rather leave a dangling row than an unreachable
        // blob if a delete fails mid-way.
        if ($attachment->storage_path && Storage::disk(self::DISK)->exists($attachment->storage_path)) {
            Storage::disk(self::DISK)->delete($attachment->storage_path);
        }
        $attachment->delete();

        return response()->json(['message' => __('Attachment removed.')]);
    }

    // ── helpers ──────────────────────────────────────────────────

    private function resolveCampaign(Request $request, int $id): Campaign|JsonResponse
    {
        $workspaceId = $request->user()->active_workspace_id;
        if (!$workspaceId) {
            return response()->json(['message' => 'No active workspace.'], 422);
        }
        $campaign = Campaign::where('workspace_id', $workspaceId)->find($id);
        if (!$campaign) {
            return response()->json(['message' => 'Campaign not found.'], 404);
        }
        return $campaign;
    }

    private function shape(Attachment $a): array
    {
        return [
            'id'                => $a->id,
            'original_filename' => $a->original_filename,
            'mime_type'         => $a->mime_type,
            'size'              => (int) $a->size,
            'size_human'        => $a->size_for_humans,
            'is_inline'         => (bool) $a->is_inline,
            'content_id'        => $a->content_id,
            'is_image'          => $a->isImage(),
            'created_at'        => $a->created_at?->toIso8601String(),
        ];
    }
}
