<?php

namespace App\Http\Controllers\Billing;

use App\Http\Controllers\Controller;
use App\Models\Order;
use App\Models\Payment;
use App\Models\PricingPlan;
use App\Models\Subscription;
use App\Models\User;
use App\Models\WebhookEvent;
use App\Services\Billing\SubscriptionTransitionService;
use App\Services\Keywords\KeywordQuotaService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

class PakasirController extends Controller
{
    public function __construct(
        private readonly SubscriptionTransitionService $subscriptionTransition,
        private readonly KeywordQuotaService $keywordQuota
    ) {}

    public function createTransaction(Request $request): JsonResponse
    {
        $data = $request->validate([
            'plan_code' => 'required|string|in:personal_monthly,personal_annual,personal_lifetime',
        ]);

        $user = $request->user();
        if (! $user) {
            return response()->json(['error' => 'auth_required'], 401);
        }

        if ($this->hasActiveLifetimeSubscription((int) $user->id)) {
            return response()->json(['error' => 'lifetime_active'], 409);
        }

        if ($cooldownPayload = $this->pendingCheckoutCooldown((int) $user->id)) {
            return response()->json($cooldownPayload, 409);
        }

        $config = config('dewemoji.billing.providers.pakasir', []);
        $enabled = (bool) ($config['enabled'] ?? false);
        $apiBase = rtrim((string) ($config['api_base'] ?? ''), '/');
        $apiKey = (string) ($config['api_key'] ?? '');
        $project = (string) ($config['project'] ?? '');
        $timeout = (int) ($config['timeout'] ?? 10);

        if (! $enabled || $apiBase === '' || $apiKey === '' || $project === '') {
            return response()->json(['error' => 'pakasir_not_configured'], 422);
        }

        $amountIdr = $this->resolvePlanAmountIdr($data['plan_code']);
        if ($amountIdr <= 0) {
            return response()->json(['error' => 'invalid_plan_amount'], 422);
        }

        $this->subscriptionTransition->cancelPendingForUser((int) $user->id);

        $order = Order::create([
            'user_id' => $user->id,
            'plan_code' => $data['plan_code'],
            'type' => $data['plan_code'] === 'personal_lifetime' ? 'one_time' : 'subscription',
            'currency' => 'IDR',
            'amount' => $amountIdr,
            'status' => 'pending',
            'provider' => 'pakasir',
        ]);

        $orderRef = 'DW-'.$order->id.'-'.now()->format('ymdHis');
        $payload = [
            'project' => $project,
            'order_id' => $orderRef,
            'amount' => $amountIdr,
            'api_key' => $apiKey,
            'description' => 'Dewemoji '.$data['plan_code'].' for '.$user->email,
        ];

        $endpoint = $apiBase.'/api/transactioncreate/qris';
        // Pakasir expects form payloads; keep JSON as fallback for provider-side variations.
        $res = Http::asForm()->timeout($timeout)->post($endpoint, $payload);
        if (! $res->successful()) {
            $res = Http::timeout($timeout)->post($endpoint, $payload);
        }

        if (! $res->successful()) {
            Log::warning('Pakasir create transaction failed', [
                'status' => $res->status(),
                'endpoint' => $endpoint,
                'body' => $res->body(),
            ]);

            return response()->json(['error' => 'pakasir_create_failed'], 502);
        }

        $body = $res->json();
        if (! is_array($body)) {
            Log::warning('Pakasir create transaction invalid response', [
                'endpoint' => $endpoint,
                'body' => $res->body(),
            ]);

            return response()->json(['error' => 'pakasir_invalid_response'], 502);
        }

        [$paymentNumber, $status, $expiredAt, $totalPayment] = $this->extractPakasirPayload($body, $amountIdr);
        if ($paymentNumber === '') {
            Log::warning('Pakasir create transaction missing QR value', [
                'endpoint' => $endpoint,
                'body' => $body,
            ]);

            return response()->json(['error' => 'pakasir_invalid_response'], 502);
        }

        $order->update([
            'provider_ref' => $orderRef,
            'status' => $status === 'success' ? 'pending' : 'pending',
        ]);

        Payment::create([
            'user_id' => $user->id,
            'order_id' => $order->id,
            'provider' => 'pakasir',
            'type' => $data['plan_code'] === 'personal_lifetime' ? 'one_time' : 'subscription',
            'plan_code' => $data['plan_code'],
            'currency' => 'IDR',
            'amount' => $amountIdr,
            'status' => 'pending',
            // Keep provider_ref short (column is varchar(100)); QR payload can be much longer.
            'provider_ref' => $this->safeProviderRef($paymentNumber, $orderRef),
            'raw_payload' => $body,
        ]);

        return response()->json([
            'ok' => true,
            'order_id' => $orderRef,
            'payment_number' => $paymentNumber,
            'amount' => $amountIdr,
            'total_payment' => $totalPayment,
            'expired_at' => $expiredAt,
            'status' => $status,
        ]);
    }

    /**
     * @param  array<string,mixed>  $body
     * @return array{0:string,1:string,2:string,3:int}
     */
    private function extractPakasirPayload(array $body, int $amountFallback): array
    {
        $payment = $body;
        if (is_array($body['payment'] ?? null)) {
            $payment = $body['payment'];
        } elseif (is_array($body['data'] ?? null)) {
            $payment = $body['data'];
        }

        $paymentNumber = (string) (
            $payment['payment_number']
            ?? $payment['qris_string']
            ?? $payment['qr_string']
            ?? $payment['qr_value']
            ?? ''
        );

        $status = (string) ($payment['status'] ?? $body['status'] ?? 'pending');
        $expiredAt = (string) (
            $payment['expired_at']
            ?? $payment['expires_at']
            ?? $payment['expired']
            ?? ''
        );

        $totalPayment = (int) (
            $payment['total_payment']
            ?? $payment['amount']
            ?? $body['total_payment']
            ?? $body['amount']
            ?? $amountFallback
        );

        return [$paymentNumber, $status, $expiredAt, $totalPayment];
    }

    private function safeProviderRef(string $paymentNumber, string $orderRef): string
    {
        $candidate = trim($paymentNumber);
        if ($candidate !== '' && mb_strlen($candidate) <= 100) {
            return $candidate;
        }

        return $orderRef;
    }

    /**
     * @return array<string,mixed>|null
     */
    private function pendingCheckoutCooldown(int $userId): ?array
    {
        $cooldown = (int) config('dewemoji.billing.pending_cooldown_seconds', 120);
        if ($cooldown <= 0) {
            return null;
        }

        $pending = Payment::query()
            ->where('user_id', $userId)
            ->where('status', 'pending')
            ->orderByDesc('id')
            ->first();
        if (! $pending || ! $pending->created_at) {
            return null;
        }

        $age = max(0, now()->getTimestamp() - $pending->created_at->getTimestamp());
        $remaining = $cooldown - $age;
        if ($remaining <= 0) {
            return null;
        }

        return [
            'error' => 'pending_cooldown',
            'retry_after' => (int) $remaining,
            'pending_payment_id' => (int) $pending->id,
            'provider' => (string) ($pending->provider ?? ''),
            'created_at' => $pending->created_at->toIso8601String(),
        ];
    }

    public function cancelPending(Request $request): JsonResponse
    {
        $user = $request->user();
        if (! $user) {
            return response()->json(['error' => 'auth_required'], 401);
        }

        $orderRef = (string) $request->input('order_id', '');

        $orderQuery = Order::where('user_id', $user->id)
            ->where('provider', 'pakasir')
            ->where('status', 'pending');

        if ($orderRef !== '') {
            $orderQuery->where('provider_ref', $orderRef);
        }

        $order = $orderQuery->orderByDesc('id')->first();
        if (! $order) {
            return response()->json(['ok' => true, 'canceled' => false, 'cancelled' => false]);
        }

        $order->update(['status' => 'canceled']);
        Payment::where('order_id', $order->id)->where('status', 'pending')->update(['status' => 'canceled']);
        Subscription::where('user_id', $user->id)
            ->where('provider', 'pakasir')
            ->where('status', 'pending')
            ->update(['status' => 'canceled']);

        return response()->json(['ok' => true, 'canceled' => true, 'cancelled' => true]);
    }

    public function paymentStatus(Request $request): JsonResponse
    {
        $user = $request->user();
        if (! $user) {
            return response()->json(['error' => 'auth_required'], 401);
        }

        $orderRef = trim((string) $request->input('order_id', ''));
        $query = Order::where('user_id', $user->id)->where('provider', 'pakasir');
        if ($orderRef !== '') {
            $query->where('provider_ref', $orderRef);
        }

        $order = $query->orderByDesc('id')->first();
        if (! $order) {
            return response()->json(['ok' => true, 'found' => false, 'status' => null, 'paid' => false]);
        }

        return response()->json([
            'ok' => true,
            'found' => true,
            'status' => $order->status,
            'paid' => $order->status === 'paid',
            'order_id' => $order->provider_ref,
        ]);
    }

    public function webhook(Request $request): JsonResponse
    {
        $payload = $request->all();
        $eventId = (string) ($payload['id'] ?? $payload['event_id'] ?? Str::uuid());
        $eventType = (string) ($payload['event_type'] ?? $payload['status'] ?? 'event');

        WebhookEvent::create([
            'provider' => 'pakasir',
            'event_id' => $eventId,
            'event_type' => $eventType,
            'status' => 'received',
            'payload' => $payload,
            'headers' => $request->headers->all(),
            'received_at' => now(),
        ]);

        $this->handlePakasirPayload($payload);

        return response()->json(['ok' => true]);
    }

    private function handlePakasirPayload(array $payload): void
    {
        $data = $payload;
        if (is_array($payload['payment'] ?? null)) {
            $data = $payload['payment'];
        } elseif (is_array($payload['data'] ?? null)) {
            $data = $payload['data'];
        }

        $status = strtolower((string) ($data['status'] ?? $payload['status'] ?? ''));
        if (! in_array($status, ['paid', 'success', 'settlement', 'completed'], true)) {
            Log::info('Pakasir webhook ignored: status not paid', [
                'status' => $status,
                'payload_status' => $payload['status'] ?? null,
            ]);

            return;
        }

        $orderId = trim((string) (
            $data['order_id']
            ?? $payload['order_id']
            ?? $data['merchant_ref']
            ?? $payload['merchant_ref']
            ?? $data['invoice_id']
            ?? $payload['invoice_id']
            ?? ''
        ));
        if ($orderId === '') {
            Log::warning('Pakasir webhook paid event missing order reference', ['payload' => $payload]);

            return;
        }

        $order = Order::where('provider', 'pakasir')->where('provider_ref', $orderId)->first();
        if (! $order) {
            if (preg_match('/^DW-(\d+)-/', $orderId, $matches) === 1) {
                $order = Order::where('provider', 'pakasir')->where('id', (int) $matches[1])->first();
            }
        }
        if (! $order) {
            Log::warning('Pakasir webhook order not found', ['order_id' => $orderId, 'payload' => $payload]);

            return;
        }

        $order->update(['status' => 'paid']);
        Payment::where('order_id', $order->id)->update(['status' => 'paid']);

        $user = User::find($order->user_id);
        if (! $user) {
            return;
        }

        $expiresAt = null;
        if ($order->plan_code === 'personal_monthly') {
            $expiresAt = now()->addMonth();
        } elseif ($order->plan_code === 'personal_annual') {
            $expiresAt = now()->addYear();
        }

        Subscription::updateOrCreate(
            [
                'provider' => 'pakasir',
                'provider_ref' => $orderId,
            ],
            [
                'user_id' => $user->id,
                'plan' => $order->plan_code,
                'status' => 'active',
                'started_at' => now(),
                'expires_at' => $expiresAt,
            ]
        );

        User::where('id', $user->id)->update(['tier' => 'personal']);
        $this->keywordQuota->enforceForUser((int) $user->id, 'personal');
        $this->subscriptionTransition->settleForActivatedPlan(
            (int) $user->id,
            'pakasir',
            $orderId,
            (string) $order->plan_code
        );
    }

    private function resolvePlanAmountIdr(string $planCode): int
    {
        $plan = PricingPlan::where('code', $planCode)->where('status', 'active')->first();
        if ($plan) {
            return (int) $plan->amount;
        }

        $defaults = collect(config('dewemoji.pricing.defaults', []))->keyBy('code');
        $fallback = $defaults->get($planCode);
        if (! $fallback) {
            return 0;
        }

        return (int) ($fallback['amount'] ?? 0);
    }

    private function hasActiveLifetimeSubscription(int $userId): bool
    {
        return Subscription::query()
            ->where('user_id', $userId)
            ->where('plan', 'personal_lifetime')
            ->where('status', 'active')
            ->where(function ($query) {
                $query->whereNull('expires_at')
                    ->orWhere('expires_at', '>', now());
            })
            ->exists();
    }
}
