<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Models\ReqLog;
use App\Models\SMSActivity;
use App\Models\SmsCount;
use App\Models\SmsMessage;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Symfony\Component\HttpFoundation\Response;

class GatewaySmsController extends Controller
{
    public function status(Request $request): JsonResponse
    {
        $userId = $request->user()->id;
        $userDevice = $request->attributes->get('user_device');
        $deviceId = null;
        if ($userDevice) {
            $deviceId = $userDevice->id;
        } elseif ($request->filled('device_id')) {
            $devInput = $request->input('device_id');
            $foundDev = \App\Models\UserDevice::where('user_id', $userId)
                ->where(function ($q) use ($devInput) {
                    $q->where('id', $devInput)->orWhere('device_id', $devInput);
                })->first();
            $deviceId = $foundDev ? $foundDev->id : (is_numeric($devInput) ? (int) $devInput : null);
        }

        $req = new ReqLog();
        $req->req_date = date("Y-m-d H:i:s");
        $req->url = json_encode($request->all());
        $req->save();

        // Auto-recover stale processing messages (older than 5 minutes) back to pending
        SmsMessage::where('user_id', $userId)
            ->where('status', SmsMessage::STATUS_PROCESSING)
            ->where('claimed_at', '<', now()->subMinutes(5))
            ->update([
                'status' => SmsMessage::STATUS_PENDING,
                'claimed_at' => null,
            ]);

        // Parse any incoming counts from request body or query parameter
        $rawCounts = $request->input('counts', $request->input('sms_counts'));
        if (is_string($rawCounts)) {
            $rawCounts = json_decode($rawCounts, true);
        }

        if (is_array($rawCounts) && !empty($rawCounts)) {
            $this->storeCountsPayload($userId, $rawCounts);
        } else {
            // Auto-compute backend API queue message counts for this user for current month
            $currentMonth = now()->format('Y-m');
            $apiTotal = SmsMessage::query()
                ->where('user_id', $userId)
                ->whereYear('created_at', now()->year)
                ->whereMonth('created_at', now()->month)
                ->count();
            $apiSent = SmsMessage::query()
                ->where('user_id', $userId)
                ->whereYear('created_at', now()->year)
                ->whereMonth('created_at', now()->month)
                ->where('status', 'sent')
                ->count();
            $apiFailed = SmsMessage::query()
                ->where('user_id', $userId)
                ->whereYear('created_at', now()->year)
                ->whereMonth('created_at', now()->month)
                ->where('status', 'failed')
                ->count();

            SmsCount::query()->updateOrCreate(
                [
                    'user_id' => $userId,
                    'month' => $currentMonth,
                    'operator_name' => '',
                    'operator_number' => '',
                    'sim_slot' => -1,
                    'type' => SmsCount::TYPE_API,
                ],
                [
                    'total_request' => $apiTotal,
                    'total_sent' => $apiSent,
                    'total_failed' => $apiFailed,
                    'last_update' => now(),
                ]
            );

            // Ensure Type 1 row exists for current month
            SmsCount::query()->firstOrCreate(
                [
                    'user_id' => $userId,
                    'month' => $currentMonth,
                    'operator_name' => '',
                    'operator_number' => '',
                    'sim_slot' => -1,
                    'type' => SmsCount::TYPE_PHONE,
                ],
                [
                    'total_request' => 0,
                    'total_sent' => 0,
                    'total_failed' => 0,
                    'last_update' => now(),
                ]
            );
        }

        $pendingQuery = SmsMessage::where('user_id', $userId)
            ->where('status', SmsMessage::STATUS_PENDING);

        if ($deviceId) {
            $pendingQuery->where(function ($q) use ($deviceId) {
                $q->whereNull('device_id')
                  ->orWhere('device_id', 0)
                  ->orWhere('device_id', $deviceId);
            });
        }

        $totalPending = $pendingQuery->count();

        $requestedMonth = $request->query('month');
        $query = SmsCount::query()->where('user_id', $userId);
        if (!empty($requestedMonth)) {
            $query->where('month', $requestedMonth);
        }

        $counts = $query
            ->orderByDesc('month')
            ->orderBy('sim_slot')
            ->orderBy('operator_name')
            ->orderBy('type')
            ->get()
            ->map(fn (SmsCount $c) => $this->serializeCount($c));

        $req->response = $totalPending;
        $req->save();

        return response()->json([
            'data' => [
                'connected' => true,
                'pending' => $totalPending,
                'user_id' => $userId,
                'device_id' => $deviceId,
                'counts' => $counts,
            ],
        ]);
    }

    public function counts(Request $request): JsonResponse
    {
        $userId = $request->user()->id;
        $userDevice = $request->attributes->get('user_device');
        $deviceId = null;
        if ($userDevice) {
            $deviceId = $userDevice->id;
        } elseif ($request->filled('device_id')) {
            $devInput = $request->input('device_id');
            $foundDev = \App\Models\UserDevice::where('user_id', $userId)
                ->where(function ($q) use ($devInput) {
                    $q->where('id', $devInput)->orWhere('device_id', $devInput);
                })->first();
            $deviceId = $foundDev ? $foundDev->id : (is_numeric($devInput) ? (int) $devInput : null);
        }

        $rawCounts = $request->input('counts', $request->input('sms_counts'));
        if (is_string($rawCounts)) {
            $rawCounts = json_decode($rawCounts, true);
        }

        if (is_array($rawCounts) && !empty($rawCounts)) {
            $this->storeCountsPayload($userId, $rawCounts);
        }

        $requestedMonth = $request->query('month', $request->input('month'));
        $query = SmsCount::query()->where('user_id', $userId);
        if (!empty($requestedMonth)) {
            $query->where('month', $requestedMonth);
        }

        $counts = $query
            ->orderByDesc('month')
            ->orderBy('sim_slot')
            ->orderBy('operator_name')
            ->orderBy('type')
            ->get()
            ->map(fn (SmsCount $c) => $this->serializeCount($c));

        return response()->json([
            'data' => [
                'user_id' => $userId,
                'device_id' => $deviceId,
                'counts' => $counts,
            ],
        ]);
    }

    /**
     * @param array<int, mixed> $rawCounts
     */
    private function storeCountsPayload(int $userId, array $rawCounts): void
    {
        foreach ($rawCounts as $item) {
            if (!is_array($item)) {
                continue;
            }

            $month = isset($item['month']) && is_string($item['month']) && trim($item['month']) !== ''
                ? trim($item['month'])
                : now()->format('Y-m');

            $operatorName = isset($item['operator_name']) ? (string) $item['operator_name'] : (string) ($item['operatorName'] ?? '');
            $operatorName = substr(trim($operatorName), 0, 64);

            $operatorNumber = isset($item['operator_number']) ? (string) $item['operator_number'] : (string) ($item['operatorNumber'] ?? '');
            $operatorNumber = substr(trim($operatorNumber), 0, 32);

            $simSlot = isset($item['sim_slot']) ? (int) $item['sim_slot'] : (int) ($item['simSlot'] ?? -1);

            $type = (int) ($item['type'] ?? SmsCount::TYPE_PHONE);
            if (!in_array($type, [SmsCount::TYPE_PHONE, SmsCount::TYPE_API], true)) {
                $type = SmsCount::TYPE_PHONE;
            }

            $totalReq = max(0, (int) ($item['total_request'] ?? $item['totalRequest'] ?? 0));
            $totalSent = max(0, (int) ($item['total_sent'] ?? $item['totalSent'] ?? 0));
            $totalFailed = max(0, (int) ($item['total_failed'] ?? $item['totalFailed'] ?? 0));
            $rawUpdate = $item['last_update'] ?? $item['lastUpdate'] ?? null;
            $lastUpdate = !empty($rawUpdate)
                ? Carbon::parse($rawUpdate)
                : now();

            SmsCount::query()->updateOrCreate(
                [
                    'user_id' => $userId,
                    'month' => $month,
                    'operator_name' => $operatorName,
                    'operator_number' => $operatorNumber,
                    'sim_slot' => $simSlot,
                    'type' => $type,
                ],
                [
                    'total_request' => $totalReq,
                    'total_sent' => $totalSent,
                    'total_failed' => $totalFailed,
                    'last_update' => $lastUpdate,
                ]
            );
        }
    }

    /**
     * @return array<string, mixed>
     */
    private function serializeCount(SmsCount $c): array
    {
        return [
            'id' => $c->id,
            'month' => $c->month,
            'operator_name' => $c->operator_name,
            'operator_number' => $c->operator_number,
            'sim_slot' => $c->sim_slot,
            'type' => $c->type,
            'total_request' => $c->total_request,
            'total_sent' => $c->total_sent,
            'total_failed' => $c->total_failed,
            'last_update' => $c->last_update instanceof \DateTimeInterface
                ? $c->last_update->toIso8601String()
                : ($c->last_update ? (string) $c->last_update : null),
        ];
    }

    public function claim(Request $request): JsonResponse
    {
        $req = new ReqLog();
        $req->req_date = date("Y-m-d H:i:s");
        $req->url = json_encode($request->all());
        $req->save();

        $userId = $request->user()->id;
        $userDevice = $request->attributes->get('user_device');
        $deviceId = null;
        if ($userDevice) {
            $deviceId = $userDevice->id;
        } elseif ($request->filled('device_id')) {
            $devInput = $request->input('device_id');
            $foundDev = \App\Models\UserDevice::where('user_id', $userId)
                ->where(function ($q) use ($devInput) {
                    $q->where('id', $devInput)->orWhere('device_id', $devInput);
                })->first();
            $deviceId = $foundDev ? $foundDev->id : (is_numeric($devInput) ? (int) $devInput : null);
        }

        $limit = max(1, min(100, (int) $request->input('limit', $request->input('chunk', 1))));

        if ($limit > 1) {
            $messagesData = DB::transaction(function () use ($userId, $limit, $deviceId): array {
                $messages = SmsMessage::query()
                    ->where('user_id', $userId)
                    ->where('status', SmsMessage::STATUS_PENDING)
                    ->oldest()
                    ->lockForUpdate()
                    ->take($limit)
                    ->get();

                if ($messages->isNotEmpty()) {
                    $ids = $messages->pluck('id')->all();
                    SmsMessage::whereIn('id', $ids)->update([
                        'status' => SmsMessage::STATUS_PROCESSING,
                        'attempts' => DB::raw('attempts + 1'),
                        'claimed_at' => now(),
                        'device_id' => $deviceId,
                        'last_error' => null,
                    ]);

                    return $messages->map(fn (SmsMessage $m) => $this->serialize($m->fresh()))->all();
                }

                return [];
            });

            return response()->json([
                'data' => $messagesData,
                'count' => count($messagesData),
            ]);
        }

        $smsMessageData = DB::transaction(function () use ($userId, $deviceId): ?array {
            $message = SmsMessage::query()
                ->where('user_id', $userId)
                ->where('status', SmsMessage::STATUS_PENDING)
                ->oldest()
                ->lockForUpdate()
                ->first();

            if ($message) {
                $message->update([
                    'status' => SmsMessage::STATUS_PROCESSING,
                    'attempts' => $message->attempts + 1,
                    'claimed_at' => now(),
                    'device_id' => $deviceId,
                    'last_error' => null,
                ]);

                return $this->serialize($message->fresh());
            }

            return null;
        });

        $req->response = json_encode($smsMessageData);
        $req->save();

        return response()->json([
            'data' => $smsMessageData,
        ]);
    }

    public function release(Request $request): JsonResponse
    {
        $userId = $request->user()->id;
        $ids = $request->input('ids', []);

        $query = SmsMessage::where('user_id', $userId)
            ->where('status', SmsMessage::STATUS_PROCESSING);

        if (is_array($ids) && !empty($ids)) {
            $query->whereIn('id', $ids);
        }

        $releasedCount = $query->update([
            'status' => SmsMessage::STATUS_PENDING,
            'claimed_at' => null,
        ]);

        return response()->json([
            'status' => 2000,
            'message' => "Successfully released {$releasedCount} message(s) back to pending queue.",
            'released_count' => $releasedCount,
        ]);
    }

    public function updateStatus(Request $request, $smsMessage): JsonResponse
    {
        $messageId = is_object($smsMessage) ? $smsMessage->id : (int) $smsMessage;
        $userId = $request->user()->id;
        $userDevice = $request->attributes->get('user_device');
        $deviceId = $userDevice ? $userDevice->id : null;

        $validated = $request->validate([
            'status' => ['required', Rule::in(['sent', 'failed'])],
            'error' => ['nullable', 'string'],
        ]);

        $isSent = $validated['status'] === 'sent';

        $targetMessage = SmsMessage::query()
            ->where('id', $messageId)
            ->where('user_id', $userId)
            ->first();

        if (! $targetMessage) {
            return response()->json(['message' => 'Message not found.'], Response::HTTP_NOT_FOUND);
        }

        $updateData = [
            'status' => $validated['status'],
            'sent_at' => $isSent ? now() : null,
            'failed_at' => $isSent ? null : now(),
            'last_error' => $isSent ? null : ($validated['error'] ?? null),
        ];
        if ($deviceId && empty($targetMessage->device_id)) {
            $updateData['device_id'] = $deviceId;
        }

        $targetMessage->update($updateData);

        return response()->json([
            'data' => [
                'id' => $targetMessage->id,
                'device_id' => $targetMessage->device_id,
                'recipient' => $targetMessage->recipient,
                'status' => $validated['status'],
                'sent_at' => $isSent ? now()->toIso8601String() : null,
                'failed_at' => $isSent ? null : now()->toIso8601String(),
                'last_error' => $isSent ? null : ($validated['error'] ?? null),
            ],
        ]);
    }

    /**
     * @return array<string, mixed>
     */
    private function serialize(SmsMessage $smsMessage): array
    {
        return [
            'id' => $smsMessage->id,
            'device_id' => $smsMessage->device_id,
            'recipient' => $smsMessage->recipient,
            'message' => $smsMessage->message,
            'status' => $smsMessage->status,
            'attempts' => $smsMessage->attempts,
            'claimed_at' => $smsMessage->claimed_at instanceof \DateTimeInterface
                ? $smsMessage->claimed_at->toIso8601String()
                : ($smsMessage->claimed_at ? (string) $smsMessage->claimed_at : null),
            'sent_at' => $smsMessage->sent_at instanceof \DateTimeInterface
                ? $smsMessage->sent_at->toIso8601String()
                : ($smsMessage->sent_at ? (string) $smsMessage->sent_at : null),
            'failed_at' => $smsMessage->failed_at instanceof \DateTimeInterface
                ? $smsMessage->failed_at->toIso8601String()
                : ($smsMessage->failed_at ? (string) $smsMessage->failed_at : null),
            'created_at' => $smsMessage->created_at instanceof \DateTimeInterface
                ? $smsMessage->created_at->toIso8601String()
                : ($smsMessage->created_at ? (string) $smsMessage->created_at : null),
            'last_error' => $smsMessage->last_error,
        ];
    }
}
