<?php
declare(strict_types=1);

namespace App\Controllers;

use App\Core\{Auth, Csrf, Database, Response};
use App\Models\{CreatorBalance, Order, Package, Payment, Setting, Subscription};
use App\Services\Payment\PaymentProviderManager;
use PDO;
use Throwable;
use function App\Core\url;

class PaymentController extends BaseController
{
    private ?array $jsonPayload = null;

    public function checkout(?string $packageId = null): string|Response
    {
        if (!Auth::check()) {
            return Response::redirect(url('/giris-yap'));
        }

        $sitePlanSlug = trim((string) ($_GET['site_plan'] ?? ''));
        if ($sitePlanSlug !== '') {
            return $this->sitePlanCheckout($sitePlanSlug);
        }

        $id = (int) ($packageId ?? ($_GET['package_id'] ?? 0));
        $orderModel = new Order();
        $periodType = $orderModel->normalizePeriod((string) ($_GET['period'] ?? 'monthly'));

        if ($id <= 0) {
            return $this->view('main', 'payment/failed', [
                'title' => 'Odeme baslatilamadi',
                'message' => 'Paket bilgisi bulunamadi.',
            ]);
        }

        try {
            $package = (new Package())->findPublic($id);
            if (!$package || ($package['status'] ?? 'inactive') !== 'active') {
                return $this->view('main', 'payment/failed', [
                    'title' => 'Paket aktif degil',
                    'message' => 'Bu paket su anda odemeye acik degil.',
                ]);
            }

            $providerManager = new PaymentProviderManager($this->config);
            $provider = $providerManager->active();
            $amount = $orderModel->priceForPeriod($package, $periodType);
            $financials = $orderModel->financials($amount, $this->financeSettings());

            return $this->view('main', 'payment/checkout', [
                'title' => 'Checkout',
                'package' => $package,
                'periodType' => $periodType,
                'periodMonths' => $orderModel->periodMonths($periodType),
                'financials' => $financials,
                'providerKey' => $providerManager->activeKey(),
                'provider' => $provider,
                'csrfToken' => Csrf::token(),
            ]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'payment.checkout');

            return $this->view('main', 'payment/failed', [
                'title' => 'Odeme baslatilamadi',
                'message' => 'Odeme ekrani su anda hazirlanamadi.',
            ]);
        }
    }

    public function createSession(): Response
    {
        if (!Auth::check()) {
            return Response::json(['ok' => false, 'message' => 'Odeme baslatmak icin giris yapmalisin.'], 403);
        }

        $payload = $this->payload();
        if (!Csrf::verify((string) ($payload['_csrf'] ?? ''))) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        $packageId = (int) ($payload['package_id'] ?? 0);
        $periodType = (new Order())->normalizePeriod((string) ($payload['period_type'] ?? $payload['period'] ?? 'monthly'));

        if (!empty($payload['site_plan_id']) || !empty($payload['site_plan_slug'])) {
            return Response::json([
                'ok' => false,
                'message' => 'Site Premium odeme akisi veri modeliyle hazir; provider baglantisi TODO.',
            ], 422);
        }

        if ($packageId <= 0) {
            return Response::json(['ok' => false, 'message' => 'Paket hedefi gecersiz.'], 422);
        }

        $pdo = Database::connection($this->config);

        try {
            $package = (new Package($pdo))->findPublic($packageId);
            if (!$package || ($package['status'] ?? 'inactive') !== 'active') {
                return Response::json(['ok' => false, 'message' => 'Bu paket su anda aktif degil.'], 404);
            }

            $providerManager = new PaymentProviderManager($this->config);
            $provider = $providerManager->active();
            $providerKey = $providerManager->activeKey();
            $orderModel = new Order($pdo);
            $paymentModel = new Payment($pdo);
            $amount = $orderModel->priceForPeriod($package, $periodType);
            $financials = $orderModel->financials($amount, $this->financeSettings());
            $cardBrand = $this->cardBrand((string) ($payload['card_number'] ?? ''));
            $installmentCount = max(1, (int) ($payload['installment_count'] ?? 1));
            $is3dSecure = !empty($payload['is_3d_secure']) || !empty($payload['three_d_secure']);

            if ($amount <= 0) {
                return Response::json(['ok' => false, 'message' => 'Paket fiyati gecersiz.'], 422);
            }

            $pdo->beginTransaction();

            $order = $orderModel->createPending($financials + [
                'order_no' => $orderModel->generateOrderNo(),
                'user_id' => (int) Auth::id(),
                'creator_id' => (int) ($package['creator_id'] ?? $package['user_id'] ?? 0),
                'package_id' => (int) $package['id'],
                'period_type' => $periodType,
                'currency' => (string) (($package['currency'] ?? '') ?: ($this->financeSettings()['currency'] ?? 'TRY')),
                'payment_provider' => $providerKey,
            ]);

            $payment = $paymentModel->createPending([
                'order_id' => (int) $order['id'],
                'provider' => $providerKey,
                'amount' => (float) $order['gross_amount'],
                'currency' => (string) $order['currency'],
                'card_type' => 'card',
                'card_brand' => $cardBrand,
                'installment_count' => $installmentCount,
                'is_3d_secure' => $is3dSecure,
                'raw_request' => [
                    'order_no' => $order['order_no'],
                    'package_id' => (int) $package['id'],
                    'period_type' => $periodType,
                    'amount' => (float) $order['gross_amount'],
                    'currency' => (string) $order['currency'],
                    'card_brand' => $cardBrand,
                    'installment_count' => $installmentCount,
                    'is_3d_secure' => $is3dSecure,
                ],
            ]);

            $providerResult = $provider->createPaymentSession($order + [
                'payment_id' => (int) $payment['id'],
                'package_name' => (string) ($package['name'] ?? ''),
                'callback_url' => url('/api/payment/callback/' . $providerKey),
                'success_url' => url('/payment/success/' . $order['order_no']),
                'failed_url' => url('/payment/failed/' . $order['order_no']),
                'pending_url' => url('/payment/pending/' . $order['order_no']),
                'card_number' => (string) ($payload['card_number'] ?? ''),
                'test_scenario' => (string) ($payload['test_scenario'] ?? ''),
            ]);
            $paymentModel->updateSession((int) $payment['id'], $providerResult);

            if (empty($providerResult['ok'])) {
                $paymentModel->markFailed((int) $payment['id'], (string) ($providerResult['provider_reference'] ?? ''));
                $orderModel->markFailed((int) $order['id'], (string) ($providerResult['provider_reference'] ?? ''));
                $pdo->commit();

                return Response::json([
                    'ok' => false,
                    'message' => (string) ($providerResult['message'] ?? 'Odeme provider oturumu olusturulamadi.'),
                    'provider' => $providerKey,
                    'redirect_url' => url('/payment/failed/' . $order['order_no']),
                ], 422);
            }

            $final = ['status' => 'pending', 'redirect_url' => url('/payment/pending/' . $order['order_no'])];
            if ($providerKey === 'test' && isset($providerResult['callback_payload']) && is_array($providerResult['callback_payload'])) {
                $verified = $provider->verifyCallback($providerResult['callback_payload']);
                $final = $this->applyVerifiedPayment($pdo, $providerKey, $verified, $providerResult['callback_payload']);
            }

            $pdo->commit();

            return Response::json([
                'ok' => true,
                'message' => (string) ($providerResult['message'] ?? 'Odeme oturumu olusturuldu.'),
                'provider' => $providerKey,
                'order_no' => (string) $order['order_no'],
                'payment_status' => $final['status'],
                'redirect_url' => $final['redirect_url'],
                'test_mode' => $providerKey === 'test',
            ]);
        } catch (Throwable $exception) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }

            $this->logException($exception, 'payment.createSession');

            return Response::json(['ok' => false, 'message' => 'Odeme baslatilamadi.'], 500);
        }
    }

    public function callback(string $provider): Response
    {
        $payload = $this->payload();
        $pdo = Database::connection($this->config);
        $providerKey = strtolower(trim($provider));

        try {
            $providerInstance = (new PaymentProviderManager($this->config))->get($providerKey);
            $verified = $providerInstance->verifyCallback($payload);

            $pdo->beginTransaction();
            $result = $this->applyVerifiedPayment($pdo, $providerKey, $verified, $payload);
            $pdo->commit();

            return Response::json([
                'ok' => $result['signature_valid'],
                'provider' => $providerKey,
                'order_no' => $result['order_no'],
                'status' => $result['status'],
                'signature_valid' => $result['signature_valid'],
            ], $result['signature_valid'] ? 200 : 400);
        } catch (Throwable $exception) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }

            $this->logException($exception, 'payment.callback');

            return Response::json(['ok' => false, 'message' => 'Callback islenemedi.'], 500);
        }
    }

    public function status(string $order_no): Response
    {
        if (!Auth::check()) {
            return Response::json(['ok' => false, 'message' => 'Giris yapmalisin.'], 403);
        }

        try {
            $order = (new Order())->findByOrderNo($order_no);
            if (!$order || !$this->canSeeOrder($order)) {
                return Response::json(['ok' => false, 'message' => 'Siparis bulunamadi.'], 404);
            }

            return Response::json([
                'ok' => true,
                'order_no' => (string) $order['order_no'],
                'order_status' => (string) $order['status'],
                'payment_status' => (string) ($order['payment_status'] ?? 'pending'),
                'redirect_url' => $this->redirectForStatus($order),
            ]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'payment.status');

            return Response::json(['ok' => false, 'message' => 'Siparis durumu okunamadi.'], 500);
        }
    }

    public function success(string $orderNo): string|Response
    {
        return $this->resultPage($orderNo, 'success');
    }

    public function failed(string $orderNo): string|Response
    {
        return $this->resultPage($orderNo, 'failed');
    }

    public function pending(string $orderNo): string|Response
    {
        return $this->resultPage($orderNo, 'pending');
    }

    private function resultPage(string $orderNo, string $view): string|Response
    {
        if (!Auth::check()) {
            return Response::redirect(url('/giris-yap'));
        }

        try {
            $order = (new Order())->findByOrderNo($orderNo);
            if (!$order || !$this->canSeeOrder($order)) {
                return $this->view('main', 'payment/failed', [
                    'title' => 'Siparis bulunamadi',
                    'message' => 'Bu odeme kaydina erisim iznin yok.',
                ]);
            }

            return $this->view('main', 'payment/' . $view, [
                'title' => match ($view) {
                    'success' => 'Odeme basarili',
                    'pending' => 'Odeme beklemede',
                    default => 'Odeme basarisiz',
                },
                'order' => $order,
            ]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'payment.result');

            return $this->view('main', 'payment/failed', [
                'title' => 'Odeme durumu okunamadi',
                'message' => 'Odeme sonucu su anda goruntulenemiyor.',
            ]);
        }
    }

    private function applyVerifiedPayment(PDO $pdo, string $providerKey, array $verified, array $rawPayload): array
    {
        $orderNo = (string) ($verified['order_no'] ?? '');
        $signatureValid = !empty($verified['signature_valid']);
        $status = (string) ($verified['status'] ?? 'failed');
        $orderModel = new Order($pdo);
        $paymentModel = new Payment($pdo);
        $subscriptionModel = new Subscription($pdo);
        $order = $orderNo !== '' ? $orderModel->findByOrderNo($orderNo) : null;
        $payment = $order ? $paymentModel->latestForOrder((int) $order['id']) : null;
        $providerReference = (string) ($verified['provider_reference'] ?? '');

        $this->recordCallback($pdo, [
            'provider' => $providerKey,
            'order_id' => $order ? (int) $order['id'] : null,
            'provider_reference' => $providerReference,
            'payload' => $this->sanitizePayload($rawPayload),
            'signature_valid' => $signatureValid,
            'status' => $signatureValid ? $status : 'invalid_signature',
        ]);

        if (!$signatureValid || !$order || !$payment) {
            return [
                'signature_valid' => false,
                'order_no' => $orderNo,
                'status' => 'failed',
                'redirect_url' => $orderNo !== '' ? url('/payment/failed/' . $orderNo) : url('/'),
            ];
        }

        if ($status === 'paid') {
            if ((string) ($order['status'] ?? '') === 'completed' && (string) ($payment['status'] ?? '') === 'paid') {
                return [
                    'signature_valid' => true,
                    'order_no' => $orderNo,
                    'status' => 'paid',
                    'redirect_url' => url('/payment/success/' . $orderNo),
                ];
            }

            $subscription = $subscriptionModel->activatePaidSubscription(
                (int) $order['user_id'],
                (int) $order['creator_id'],
                (int) $order['package_id'],
                (int) $order['id'],
                (string) $order['period_type']
            );
            $paymentModel->markPaid((int) $payment['id'], $providerReference);
            $orderModel->markCompleted((int) $order['id'], (int) $subscription['id'], $providerReference);
            $completedOrder = $orderModel->findByOrderNo($orderNo) ?? $order;
            (new CreatorBalance($pdo))->recordSale($completedOrder, (int) ($this->financeSettings()['balance_hold_days'] ?? 0));

            return [
                'signature_valid' => true,
                'order_no' => $orderNo,
                'status' => 'paid',
                'redirect_url' => url('/payment/success/' . $orderNo),
            ];
        }

        if ($status === 'pending') {
            $paymentModel->markPending((int) $payment['id'], $providerReference);
            $orderModel->markPending((int) $order['id'], $providerReference);

            return [
                'signature_valid' => true,
                'order_no' => $orderNo,
                'status' => 'pending',
                'redirect_url' => url('/payment/pending/' . $orderNo),
            ];
        }

        $paymentModel->markFailed((int) $payment['id'], $providerReference);
        $orderModel->markFailed((int) $order['id'], $providerReference);

        return [
            'signature_valid' => true,
            'order_no' => $orderNo,
            'status' => 'failed',
            'redirect_url' => url('/payment/failed/' . $orderNo),
        ];
    }

    private function recordCallback(PDO $pdo, array $data): void
    {
        $statement = $pdo->prepare(
            'INSERT INTO payment_callbacks
                (provider, order_id, provider_reference, payload, signature_valid, ip_address, status, created_at)
             VALUES
                (:provider, :order_id, :provider_reference, :payload, :signature_valid, :ip_address, :status, NOW())'
        );
        $statement->execute([
            'provider' => $data['provider'],
            'order_id' => $data['order_id'],
            'provider_reference' => $data['provider_reference'],
            'payload' => json_encode($data['payload'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
            'signature_valid' => !empty($data['signature_valid']) ? 1 : 0,
            'ip_address' => substr((string) ($_SERVER['REMOTE_ADDR'] ?? ''), 0, 64),
            'status' => $data['status'],
        ]);
    }

    private function sitePlanCheckout(string $slug): string
    {
        try {
            $statement = Database::connection($this->config)->prepare(
                'SELECT *
                   FROM site_plans
                  WHERE slug = :slug
                    AND status = "active"
                  LIMIT 1'
            );
            $statement->execute(['slug' => $slug]);
            $plan = $statement->fetch(PDO::FETCH_ASSOC) ?: null;

            if (!$plan) {
                return $this->view('main', 'payment/failed', [
                    'title' => 'Site Premium bulunamadi',
                    'message' => 'Secilen site premium plani aktif degil veya bulunamadi.',
                ]);
            }

            $plan['features'] = json_decode((string) ($plan['features_json'] ?? '[]'), true) ?: [];

            return $this->view('main', 'payment/site-plan', [
                'title' => 'Site Premium',
                'plan' => $plan,
            ]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'payment.sitePlanCheckout');

            return $this->view('main', 'payment/failed', [
                'title' => 'Site Premium hazirlanamadi',
                'message' => 'Site premium ekrani su anda hazirlanamadi.',
            ]);
        }
    }

    private function financeSettings(): array
    {
        try {
            $model = new Setting();
            $settings = $model->getFinancialSettings();
            if (($settings['commission_rate'] ?? '') === '') {
                $platformPercent = (float) $model->getByKey('platform_fee_percent', '10');
                $settings['commission_rate'] = (string) ($platformPercent / 100);
            }

            return [
                'site_commission_rate' => $settings['site_commission_rate'] ?? $settings['commission_rate'] ?? '0.10',
                'commission_rate' => $settings['commission_rate'] ?? $settings['site_commission_rate'] ?? '0.10',
                'tax_rate' => $settings['tax_rate'] ?? '0.20',
                'provider_fee_rate' => $settings['provider_fee_rate'] ?? '0',
                'provider_fee_fixed' => $settings['provider_fee_fixed'] ?? '0',
                'minimum_withdrawal_amount' => $settings['minimum_withdrawal_amount'] ?? '100',
                'payout_delay_minutes' => $settings['payout_delay_minutes'] ?? '20',
                'balance_hold_days' => $settings['balance_hold_days'] ?? '0',
                'currency' => $settings['currency'] ?? $settings['default_currency'] ?? 'TRY',
                'tax_calc_type' => $settings['tax_calc_type'] ?? 'gross',
                'payout_mode' => $settings['payout_mode'] ?? 'manual',
            ];
        } catch (Throwable) {
            return [
                'site_commission_rate' => 0.10,
                'commission_rate' => 0.10,
                'tax_rate' => 0.20,
                'provider_fee_rate' => 0,
                'provider_fee_fixed' => 0,
                'minimum_withdrawal_amount' => 100,
                'payout_delay_minutes' => 20,
                'balance_hold_days' => 0,
                'currency' => 'TRY',
                'tax_calc_type' => 'gross',
                'payout_mode' => 'manual',
            ];
        }
    }

    private function canSeeOrder(array $order): bool
    {
        $user = Auth::user();
        if (!$user) {
            return false;
        }

        return (string) ($user['role'] ?? '') === 'admin' || (int) ($order['user_id'] ?? 0) === (int) ($user['id'] ?? 0);
    }

    private function redirectForStatus(array $order): string
    {
        $orderNo = (string) $order['order_no'];
        $paymentStatus = (string) ($order['payment_status'] ?? '');

        if ((string) ($order['status'] ?? '') === 'completed' || $paymentStatus === 'paid') {
            return url('/payment/success/' . $orderNo);
        }

        if ((string) ($order['status'] ?? '') === 'failed' || $paymentStatus === 'failed') {
            return url('/payment/failed/' . $orderNo);
        }

        return url('/payment/pending/' . $orderNo);
    }

    private function cardBrand(string $cardNumber): string
    {
        $cardNumber = preg_replace('/\D+/', '', $cardNumber);
        if (str_starts_with($cardNumber, '4')) {
            return 'visa';
        }

        if (preg_match('/^(5[1-5]|2[2-7])/', $cardNumber)) {
            return 'mastercard';
        }

        if (str_starts_with($cardNumber, '9792')) {
            return 'troy';
        }

        return 'card';
    }

    private function sanitizePayload(array $payload): array
    {
        $blocked = ['card_number', 'cardNumber', 'pan', 'cc_number', 'cvv', 'cvc', 'security_code', 'expiry', 'exp_month', 'exp_year', 'card_holder', 'card_name'];
        foreach ($payload as $key => $value) {
            if (in_array((string) $key, $blocked, true)) {
                unset($payload[$key]);
                continue;
            }

            if (is_array($value)) {
                $payload[$key] = $this->sanitizePayload($value);
            }
        }

        return $payload;
    }

    private function payload(): array
    {
        if ($_POST !== []) {
            return $_POST;
        }

        if ($this->jsonPayload !== null) {
            return $this->jsonPayload;
        }

        $decoded = json_decode(file_get_contents('php://input') ?: '', true);
        $this->jsonPayload = is_array($decoded) ? $decoded : [];

        return $this->jsonPayload;
    }
}
