<?php
declare(strict_types=1);

namespace App\Controllers;

use App\Core\{Auth, Csrf, Database, Response};
use App\Models\{BankAccount, CreatorBalance, Setting, WithdrawalRequest};
use App\Services\Payout\ManualPayoutProvider;
use PDO;
use Throwable;

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

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

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

        try {
            $userId = (int) Auth::id();
            $settings = (new Setting())->getFinancialSettings();
            $amount = round((float) str_replace(',', '.', (string) ($payload['amount'] ?? 0)), 2);
            $minimum = (float) $settings['minimum_withdrawal_amount'];
            $bankModel = new BankAccount();
            $bankAccountId = (int) ($payload['user_bank_account_id'] ?? 0);
            $bankAccount = $bankAccountId > 0 ? $bankModel->findForUser($bankAccountId, $userId) : $bankModel->defaultForUser($userId);

            if ($amount < $minimum) {
                return Response::json(['ok' => false, 'message' => 'Minimum cekim tutari ' . number_format($minimum, 2, ',', '.') . ' ' . $settings['currency'] . '.'], 422);
            }

            if (!$bankAccount) {
                return Response::json(['ok' => false, 'message' => 'Para cekme talebi icin once banka hesabi eklemelisin.'], 422);
            }

            $balance = (new CreatorBalance())->findOrCreate($userId, (string) $settings['currency']);
            if ((float) ($balance['available_balance'] ?? 0) < $amount) {
                return Response::json(['ok' => false, 'message' => 'Cekilebilir bakiyen bu tutar icin yeterli degil.'], 422);
            }

            $siteAccount = $bankModel->defaultSiteAccount();
            $result = (new ManualPayoutProvider())->createWithdrawalRequest([
                'user_id' => $userId,
                'user_bank_account_id' => (int) $bankAccount['id'],
                'site_bank_account_id' => $siteAccount['id'] ?? null,
                'amount' => $amount,
                'currency' => (string) $settings['currency'],
                'user_note' => $this->clean((string) ($payload['user_note'] ?? ''), 500) ?: null,
            ]);
            $this->notifyAdmins((int) ($result['withdrawal']['id'] ?? 0), $amount, (string) $settings['currency']);

            return Response::json([
                'ok' => true,
                'message' => 'Para cekme talebin olusturuldu. Admin onayindan sonra yaklasik ' . (int) $settings['payout_delay_minutes'] . ' dakika icinde gonderilir.',
                'withdrawal' => $result['withdrawal'] ?? null,
            ]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'withdrawal.create');

            return Response::json(['ok' => false, 'message' => 'Para cekme talebi olusturulamadi.'], 500);
        }
    }

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

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

        try {
            $result = (new ManualPayoutProvider())->cancel((int) ($payload['id'] ?? $payload['withdrawal_id'] ?? 0), (int) Auth::id());

            return Response::json(['ok' => true, 'message' => 'Para cekme talebi iptal edildi ve bakiye geri yansitildi.', 'withdrawal' => $result['withdrawal'] ?? null]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'withdrawal.cancel');

            return Response::json(['ok' => false, 'message' => 'Talep iptal edilemedi.'], 422);
        }
    }

    public function createBankAccount(): Response
    {
        return $this->saveUserBankAccount(false);
    }

    public function updateBankAccount(): Response
    {
        return $this->saveUserBankAccount(true);
    }

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

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

        try {
            $deleted = (new BankAccount())->deleteForUser((int) ($payload['id'] ?? 0), (int) Auth::id());

            return Response::json(['ok' => $deleted, 'message' => $deleted ? 'Banka hesabi silindi.' : 'Banka hesabi bulunamadi.'], $deleted ? 200 : 404);
        } catch (Throwable $exception) {
            $this->logException($exception, 'withdrawal.deleteBank');

            return Response::json(['ok' => false, 'message' => 'Banka hesabi silinemedi.'], 500);
        }
    }

    public function zpanelList(): Response
    {
        Auth::requireAdmin();

        try {
            $status = (string) ($_GET['status'] ?? '');

            return Response::json([
                'ok' => true,
                'withdrawals' => (new WithdrawalRequest())->listForAdmin($status !== '' ? $status : null, 150),
            ]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'withdrawal.zpanelList');

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

    public function approve(): Response
    {
        return $this->adminTransition('approved');
    }

    public function processing(): Response
    {
        return $this->adminTransition('processing');
    }

    public function markSent(): Response
    {
        return $this->adminTransition('sent');
    }

    public function reject(): Response
    {
        return $this->adminTransition('rejected');
    }

    public function createSiteBankAccount(): Response
    {
        return $this->saveSiteBankAccount(false);
    }

    public function updateSiteBankAccount(): Response
    {
        return $this->saveSiteBankAccount(true);
    }

    public function deleteSiteBankAccount(): Response
    {
        Auth::requireAdmin();

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

        try {
            $deleted = (new BankAccount())->deactivateSiteAccount((int) ($payload['id'] ?? 0));
            if ($deleted) {
                $this->adminLog('site_bank_account.deactivate', 'site_bank_account', (int) ($payload['id'] ?? 0), []);
            }

            return Response::json(['ok' => $deleted, 'message' => $deleted ? 'Site banka hesabi pasife alindi.' : 'Banka hesabi bulunamadi.'], $deleted ? 200 : 404);
        } catch (Throwable $exception) {
            $this->logException($exception, 'withdrawal.deleteSiteBank');

            return Response::json(['ok' => false, 'message' => 'Site banka hesabi guncellenemedi.'], 500);
        }
    }

    public function updateFinancialSettings(): Response
    {
        Auth::requireAdmin();

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

        try {
            $settings = (new Setting())->saveFinancialSettings($payload);
            $this->adminLog('financial_settings.update', 'settings', null, $settings);

            return Response::json(['ok' => true, 'message' => 'Finans ayarlari kaydedildi.', 'settings' => $settings]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'withdrawal.financialSettings');

            return Response::json(['ok' => false, 'message' => 'Finans ayarlari kaydedilemedi.'], 422);
        }
    }

    private function saveUserBankAccount(bool $isUpdate): Response
    {
        if (!Auth::check()) {
            return Response::json(['ok' => false, 'message' => 'Giris yapmalisin.'], 403);
        }

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

        try {
            $model = new BankAccount();
            $account = $isUpdate
                ? $model->updateForUser((int) ($payload['id'] ?? 0), (int) Auth::id(), $payload)
                : $model->createForUser((int) Auth::id(), $payload);

            if (!$account) {
                return Response::json(['ok' => false, 'message' => 'Banka hesabi bulunamadi.'], 404);
            }

            return Response::json(['ok' => true, 'message' => 'Banka hesabi kaydedildi.', 'account' => $account]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'withdrawal.saveBank');

            return Response::json(['ok' => false, 'message' => $exception instanceof \InvalidArgumentException ? $exception->getMessage() : 'Banka hesabi kaydedilemedi.'], 422);
        }
    }

    private function saveSiteBankAccount(bool $isUpdate): Response
    {
        Auth::requireAdmin();

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

        try {
            $model = new BankAccount();
            $account = $isUpdate
                ? $model->updateSiteAccount((int) ($payload['id'] ?? 0), $payload)
                : $model->createSiteAccount($payload);

            if (!$account) {
                return Response::json(['ok' => false, 'message' => 'Site banka hesabi bulunamadi.'], 404);
            }

            $this->adminLog($isUpdate ? 'site_bank_account.update' : 'site_bank_account.create', 'site_bank_account', (int) $account['id'], $account);

            return Response::json(['ok' => true, 'message' => 'Site banka hesabi kaydedildi.', 'account' => $account]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'withdrawal.saveSiteBank');

            return Response::json(['ok' => false, 'message' => $exception instanceof \InvalidArgumentException ? $exception->getMessage() : 'Site banka hesabi kaydedilemedi.'], 422);
        }
    }

    private function adminTransition(string $action): Response
    {
        Auth::requireAdmin();

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

        $id = (int) ($payload['id'] ?? $payload['withdrawal_id'] ?? 0);
        $note = $this->clean((string) ($payload['admin_note'] ?? $payload['reason'] ?? ''), 1000);

        try {
            $provider = new ManualPayoutProvider();
            $result = match ($action) {
                'approved' => $provider->approve($id, (int) Auth::id(), $note ?: null),
                'processing' => $provider->markProcessing($id, (int) Auth::id(), $note ?: null),
                'sent' => $provider->markSent($id, (int) Auth::id(), [
                    'admin_note' => $note ?: null,
                    'transaction_reference' => $this->clean((string) ($payload['transaction_reference'] ?? ''), 190) ?: null,
                    'receipt_file' => $this->clean((string) ($payload['receipt_file'] ?? ''), 255) ?: null,
                ]),
                'rejected' => $provider->reject($id, (int) Auth::id(), $note ?: 'Admin tarafindan reddedildi.'),
                default => throw new \InvalidArgumentException('Gecersiz aksiyon.'),
            };
            $this->adminLog('withdrawal.' . $action, 'withdrawal_request', $id, [
                'note' => $note,
                'transaction_reference' => $payload['transaction_reference'] ?? null,
            ]);

            $delay = (int) ((new Setting())->getFinancialSettings()['payout_delay_minutes'] ?? 20);
            $message = match ($action) {
                'approved' => 'Talep onaylandi. Kullaniciya yaklasik ' . $delay . ' dakika icinde gonderilir mesaji gosterilir.',
                'processing' => 'Talep isleme alindi.',
                'sent' => 'Talep gonderildi olarak isaretlendi.',
                'rejected' => 'Talep reddedildi ve bakiye kullaniciya geri yansitildi.',
                default => 'Islem tamamlandi.',
            };

            return Response::json(['ok' => true, 'message' => $message, 'withdrawal' => $result['withdrawal'] ?? null]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'withdrawal.adminTransition');

            return Response::json(['ok' => false, 'message' => 'Admin aksiyonu uygulanamadi.'], 422);
        }
    }

    private function notifyAdmins(int $withdrawalId, float $amount, string $currency): void
    {
        if ($withdrawalId <= 0) {
            return;
        }

        try {
            $pdo = Database::connection($this->config);
            $admins = $pdo->query('SELECT id FROM users WHERE role = "admin" AND status = "active"')->fetchAll();
            $statement = $pdo->prepare(
                'INSERT INTO notifications (user_id, type, title, body, data, read_at, created_at)
                 VALUES (:user_id, "withdrawal_request", "Yeni para cekme talebi", :body, :data, NULL, NOW())'
            );
            foreach ($admins as $admin) {
                $statement->execute([
                    'user_id' => (int) $admin['id'],
                    'body' => number_format($amount, 2, ',', '.') . ' ' . $currency . ' tutarinda yeni para cekme talebi var.',
                    'data' => json_encode(['withdrawal_id' => $withdrawalId], JSON_UNESCAPED_UNICODE),
                ]);
            }
        } catch (Throwable $exception) {
            $this->logException($exception, 'withdrawal.notifyAdmins');
        }
    }

    private function adminLog(string $action, string $targetType, ?int $targetId, array $details): void
    {
        try {
            $statement = Database::connection($this->config)->prepare(
                'INSERT INTO admin_logs
                    (admin_id, action, target_type, target_id, entity_type, entity_id, ip_address, user_agent, details, created_at)
                 VALUES
                    (:admin_id, :action, :target_type, :target_id, :entity_type, :entity_id, :ip_address, :user_agent, :details, NOW())'
            );
            $statement->execute([
                'admin_id' => Auth::id(),
                'action' => $action,
                'target_type' => $targetType,
                'target_id' => $targetId,
                'entity_type' => $targetType,
                'entity_id' => $targetId,
                'ip_address' => substr((string) ($_SERVER['REMOTE_ADDR'] ?? ''), 0, 64),
                'user_agent' => substr((string) ($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 255),
                'details' => json_encode($details, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
            ]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'withdrawal.adminLog');
        }
    }

    private function validCsrf(array $payload): bool
    {
        return Csrf::verify((string) ($payload['_csrf'] ?? ($_SERVER['HTTP_X_CSRF_TOKEN'] ?? '')));
    }

    private function payload(): array
    {
        if ($_POST !== []) {
            $payload = $_POST;
            if (empty($payload['_csrf']) && !empty($_SERVER['HTTP_X_CSRF_TOKEN'])) {
                $payload['_csrf'] = (string) $_SERVER['HTTP_X_CSRF_TOKEN'];
            }

            return $payload;
        }

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

        $raw = file_get_contents('php://input') ?: '';
        $decoded = json_decode($raw, true);
        if (is_array($decoded)) {
            $this->jsonPayload = $decoded;
        } else {
            parse_str($raw, $parsed);
            $this->jsonPayload = is_array($parsed) ? $parsed : [];
        }

        if (empty($this->jsonPayload['_csrf']) && !empty($_SERVER['HTTP_X_CSRF_TOKEN'])) {
            $this->jsonPayload['_csrf'] = (string) $_SERVER['HTTP_X_CSRF_TOKEN'];
        }

        return $this->jsonPayload;
    }

    private function clean(string $value, int $limit): string
    {
        $value = trim(preg_replace('/\s+/', ' ', $value) ?: '');

        return function_exists('mb_substr') ? mb_substr($value, 0, $limit) : substr($value, 0, $limit);
    }
}
