<?php

namespace App\Controllers\Api\V1;

use App\Controllers\BaseController;
use App\Models\SubscriptionPlanModel;
use App\Services\Billing\CreditService;
use App\Services\Billing\InvoiceService;
use App\Services\Billing\PaymentService;
use App\Services\Billing\RefundService;
use App\Services\Billing\SubscriptionService;
use App\Services\Billing\WalletService;
use Config\Database;

class BillingController extends BaseController
{
    private SubscriptionService $subscriptions;
    private CreditService $credits;
    private WalletService $wallet;
    private InvoiceService $invoices;
    private PaymentService $payments;
    private RefundService $refunds;
    private \App\Services\Notification\NotificationService $notifications;

    public function initController(\CodeIgniter\HTTP\RequestInterface $request, \CodeIgniter\HTTP\ResponseInterface $response, \Psr\Log\LoggerInterface $logger)
    {
        parent::initController($request, $response, $logger);
        $this->subscriptions = new SubscriptionService();
        $this->credits       = new CreditService();
        $this->wallet        = new WalletService();
        $this->invoices      = new InvoiceService();
        $this->payments      = new PaymentService();
        $this->refunds       = new RefundService();
        $this->notifications = new \App\Services\Notification\NotificationService();
    }

    // --- Plans (public) ---

    public function plans()
    {
        return $this->jsonSuccess(['plans' => (new SubscriptionPlanModel())->activePlans()]);
    }

    // --- Subscription (Agency Owner) ---

    public function currentSubscription()
    {
        $user = $this->currentUser();
        $this->guardAgencyOwner($user);

        $data = $this->subscriptions->currentSubscriptionWithPlan($user->agency_id);
        if (! $data) {
            return $this->jsonError([['code' => 'NO_SUBSCRIPTION', 'message' => 'No active subscription found.']], 404);
        }

        return $this->jsonSuccess($data);
    }

    /**
     * Agencies can no longer self-assign a plan directly — this creates a
     * pending request instead. The plan only actually changes once a
     * Super Admin approves it (see AdminAgencyController::approvePlanChange()).
     */
    public function subscribe()
    {
        $user = $this->currentUser();
        $this->guardAgencyOwner($user);

        $rules = [
            'plan_slug'     => 'required',
            'billing_cycle' => 'required|in_list[monthly,yearly]',
        ];
        if (! $this->validate($rules)) {
            return $this->jsonValidationError($this->validator->getErrors());
        }

        $input = $this->request->getJSON(true);

        $planModel = new SubscriptionPlanModel();
        $requestedPlan = $planModel->findBySlug($input['plan_slug']);
        if (! $requestedPlan) {
            return $this->jsonError([['code' => 'INVALID_PLAN', 'message' => 'Unknown plan.']], 422);
        }

        $requestModel = new \App\Models\PlanChangeRequestModel();
        $existing = $requestModel->pendingFor($user->agency_id);
        if ($existing) {
            return $this->jsonError([['code' => 'REQUEST_PENDING', 'message' => 'You already have a plan change request awaiting review.']], 409);
        }

        $current = $this->subscriptions->currentSubscriptionWithPlan($user->agency_id);

        $requestId = $requestModel->insert([
            'agency_id'             => $user->agency_id,
            'requested_by_user_id'  => $user->id,
            'current_plan_id'       => $current['plan']['id'] ?? null,
            'requested_plan_id'     => $requestedPlan['id'],
            'billing_cycle'         => $input['billing_cycle'],
            'status'                => 'pending',
            'created_at'            => date('Y-m-d H:i:s'),
        ], true);

        // Notify every Super Admin so this shows up for review — plan
        // changes are a billing-relevant action now gated on approval.
        $agency = (new \App\Models\AgencyModel())->find($user->agency_id);
        $superAdmins = Database::connect()->table('users u')->join('roles r', 'r.id = u.role_id')->where('r.slug', 'super_admin')->get()->getResultArray();
        foreach ($superAdmins as $admin) {
            $this->notifications->send($admin['id'], 'plan_change_requested', [
                'agency_name' => $agency->company_name ?? 'An agency',
                'plan_name'   => $requestedPlan['name'],
            ]);
        }

        return $this->jsonSuccess(['request' => $requestModel->find($requestId)], [], 201);
    }

    public function pendingPlanRequest()
    {
        $user = $this->currentUser();
        $this->guardAgencyOwner($user);

        $requestModel = new \App\Models\PlanChangeRequestModel();
        $pending = $requestModel->pendingFor($user->agency_id);

        if ($pending) {
            $plan = (new SubscriptionPlanModel())->find($pending['requested_plan_id']);
            $pending['requested_plan_name'] = $plan['name'] ?? null;
        }

        return $this->jsonSuccess(['pending_request' => $pending]);
    }

    // --- Credits (Agency Owner) ---

    public function creditBalance()
    {
        $user = $this->currentUser();
        $this->guardAgencyOwner($user);

        return $this->jsonSuccess(['balance' => $this->credits->balanceFor($user->agency_id)]);
    }

    public function purchaseCredits()
    {
        $user = $this->currentUser();
        $this->guardAgencyOwner($user);

        $rules = [
            'credit_amount' => 'required|is_natural_no_zero',
            'gateway'       => 'required|in_list[stripe,paypal,jazzcash,easypaisa,bank_transfer,wallet]',
        ];
        if (! $this->validate($rules)) {
            return $this->jsonValidationError($this->validator->getErrors());
        }

        $input = $this->request->getJSON(true);
        $rate = 100; // PKR per credit — matches PaymentService::applySuccessEffect()
        $amount = $input['credit_amount'] * $rate;

        if ($input['gateway'] === 'wallet') {
            try {
                $this->wallet->debit($user->agency_id, $amount, 'credit_purchase', null, 'Credit purchase via wallet');
            } catch (\App\Exceptions\InsufficientFundsException $e) {
                return $this->jsonError([['code' => 'INSUFFICIENT_FUNDS', 'message' => $e->getMessage()]], 402);
            }
            $txId = $this->credits->credit($user->agency_id, $input['credit_amount'], 'purchase');
            return $this->jsonSuccess(['credit_transaction_id' => $txId, 'new_balance' => $this->credits->balanceFor($user->agency_id)]);
        }

        $invoice = $this->invoices->generate($user->agency_id, [
            ['description' => "{$input['credit_amount']} credits", 'quantity' => 1, 'unit_price' => $amount],
        ]);

        $charge = $this->payments->initiateCharge(
            $user->agency_id, $input['gateway'], $amount, 'PKR', 'credit_purchase', [], $invoice['invoice']['id']
        );

        return $this->jsonSuccess(['invoice' => $invoice, 'payment' => $charge], [], 201);
    }

    // --- Wallet (Agency Owner) ---

    public function walletBalance()
    {
        $user = $this->currentUser();
        $this->guardAgencyOwner($user);

        return $this->jsonSuccess([
            'balance' => $this->wallet->balanceFor($user->agency_id),
            'ledger'  => $this->wallet->ledgerFor($user->agency_id),
        ]);
    }

    public function topUpWallet()
    {
        $user = $this->currentUser();
        $this->guardAgencyOwner($user);

        $rules = [
            'amount'  => 'required|decimal|greater_than[0]',
            'gateway' => 'required|in_list[stripe,paypal,jazzcash,easypaisa,bank_transfer]',
        ];
        if (! $this->validate($rules)) {
            return $this->jsonValidationError($this->validator->getErrors());
        }

        $input = $this->request->getJSON(true);
        $charge = $this->payments->initiateCharge($user->agency_id, $input['gateway'], (float) $input['amount'], 'PKR', 'wallet_topup');

        return $this->jsonSuccess(['payment' => $charge], [], 201);
    }

    // --- Invoices (Agency Owner) ---

    public function listInvoices()
    {
        $user = $this->currentUser();
        $this->guardAgencyOwner($user);

        return $this->jsonSuccess(['invoices' => $this->invoices->forAgency($user->agency_id)]);
    }

    public function downloadInvoice(int $invoiceId)
    {
        $user = $this->currentUser();
        $this->guardAgencyOwner($user);

        $data = $this->invoices->getWithItems($invoiceId);
        if (! $data['invoice'] || (int) $data['invoice']['agency_id'] !== $user->agency_id) {
            return $this->jsonError([['code' => 'NOT_FOUND', 'message' => 'Invoice not found.']], 404);
        }

        try {
            $path = $this->invoices->generatePdf($invoiceId);
        } catch (\RuntimeException $e) {
            return $this->jsonError([['code' => 'PDF_UNAVAILABLE', 'message' => $e->getMessage()]], 501);
        }

        return $this->response->download($path, null);
    }

    // --- Refunds ---

    public function requestRefund()
    {
        $user = $this->currentUser();
        $this->guardAgencyOwner($user);

        $input = $this->request->getJSON(true);
        if (empty($input['payment_id']) || empty($input['reason'])) {
            return $this->jsonValidationError(['payment_id' => 'payment_id and reason are required.']);
        }

        try {
            $id = $this->refunds->requestRefund((int) $input['payment_id'], $user->id, $input['reason']);
        } catch (\InvalidArgumentException $e) {
            return $this->jsonError([['code' => 'REFUND_REQUEST_FAILED', 'message' => $e->getMessage()]], 422);
        }

        return $this->jsonSuccess(['refund_request_id' => $id], [], 201);
    }

    private function guardAgencyOwner($user): void
    {
        if (! $user->isAgencyOwner()) {
            throw new \App\Exceptions\ForbiddenException('This action is only available to the Agency Owner.');
        }
    }
}
