<?php

namespace App\Http\Controllers\Tenant;

use App\Http\Controllers\Controller;
use App\Models\BillPayment;
use App\Models\Customer;
use App\Services\ActivityLogger;
use App\Services\BillPaymentService;
use App\Services\TransactionCancellationService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Validation\Rule;
use Illuminate\View\View;
use InvalidArgumentException;

class BillPaymentController extends Controller
{
    public function index(Request $request): View
    {
        $validated = $request->validate([
            'search' => ['nullable', 'string', 'max:255'],
            'service_type' => ['nullable', Rule::in(array_keys($this->serviceTypes()))],
            'status' => ['nullable', Rule::in(array_keys($this->statuses()))],
            'from' => ['nullable', 'date'],
            'to' => ['nullable', 'date'],
        ]);

        $from = filled($validated['from'] ?? null) ? Carbon::parse($validated['from'])->startOfDay() : now()->startOfMonth();
        $to = filled($validated['to'] ?? null) ? Carbon::parse($validated['to'])->endOfDay() : now()->endOfMonth();
        $search = trim((string) ($validated['search'] ?? ''));
        $user = $request->user();

        $baseQuery = BillPayment::with(['customer', 'recordedBy', 'canceledBy', 'userWallet.user'])
            ->whereBetween('created_at', [$from, $to])
            ->where(function ($query) use ($user): void {
                $query->whereNull('customer_id')
                    ->orWhereHas('customer', fn ($customerQuery) => $customerQuery->visibleTo($user));
            })
            ->when(filled($validated['service_type'] ?? null), fn ($query) => $query->where('service_type', $validated['service_type']))
            ->when(filled($validated['status'] ?? null), fn ($query) => $query->where('status', $validated['status']))
            ->when($search !== '', function ($query) use ($search): void {
                $query->where(function ($nested) use ($search): void {
                    $nested->where('reference', 'like', "%{$search}%")
                        ->orWhere('provider', 'like', "%{$search}%")
                        ->orWhere('customer_identifier', 'like', "%{$search}%")
                        ->orWhere('customer_name', 'like', "%{$search}%")
                        ->orWhere('provider_reference', 'like', "%{$search}%");
                });
            });

        return view('tenant.bill-payments.index', [
            'billPayments' => (clone $baseQuery)->latest()->paginate(15)->withQueryString(),
            'totalVolume' => (clone $baseQuery)->where('status', BillPayment::STATUS_SUCCESSFUL)->sum('total_amount'),
            'totalEarnings' => (float) (clone $baseQuery)->where('status', BillPayment::STATUS_SUCCESSFUL)->sum('fee')
                + (float) (clone $baseQuery)->where('status', BillPayment::STATUS_SUCCESSFUL)->sum('commission'),
            'pendingCount' => (clone $baseQuery)->where('status', BillPayment::STATUS_PENDING)->count(),
            'customers' => Customer::visibleTo($user)->orderBy('first_name')->orderBy('last_name')->get(),
            'serviceTypes' => $this->serviceTypes(),
            'providers' => $this->providers(),
            'statuses' => $this->statuses(),
            'paymentMethods' => $this->paymentMethods(),
            'from' => $from,
            'to' => $to,
            'filters' => $validated + ['search' => $search],
        ]);
    }

    public function store(Request $request, BillPaymentService $billPayments): RedirectResponse
    {
        $tenantId = $request->user()->tenant_id;

        $validated = $request->validate([
            'customer_id' => [
                'nullable',
                'integer',
                Rule::exists('customers', 'id')->where('tenant_id', $tenantId),
            ],
            'service_type' => ['required', Rule::in(array_keys($this->serviceTypes()))],
            'provider' => ['required', 'string', 'max:255'],
            'customer_identifier' => ['required', 'string', 'max:255'],
            'customer_name' => ['nullable', 'string', 'max:255'],
            'package_name' => ['nullable', 'string', 'max:255'],
            'amount' => ['required', 'numeric', 'min:0.01'],
            'fee' => ['nullable', 'numeric', 'min:0'],
            'commission' => ['nullable', 'numeric', 'min:0'],
            'payment_method' => ['required', Rule::in(array_keys($this->paymentMethods()))],
            'status' => ['required', Rule::in(array_keys($this->statuses()))],
            'provider_reference' => ['nullable', 'string', 'max:255'],
            'token' => ['nullable', 'string', 'max:2000'],
            'notes' => ['nullable', 'string', 'max:2000'],
        ]);

        if (! empty($validated['customer_id'])) {
            Customer::visibleTo($request->user())->findOrFail($validated['customer_id']);
        }

        try {
            $billPayment = $billPayments->counterPurchase($request->user(), $validated);
        } catch (InvalidArgumentException $exception) {
            return back()->withInput()->with('warning', $exception->getMessage());
        }

        $driver = data_get($billPayment->provider_response, 'driver');
        $message = in_array($driver, ['vtpass', 'flutterwave'], true)
            ? 'Bill payment sent to provider. Current status: '.ucfirst($billPayment->status).'.'
            : 'Bill payment recorded.';

        return redirect()->route('tenant.bill-payments.index')->with('status', $message);
    }

    public function updateStatus(Request $request, BillPayment $billPayment): RedirectResponse
    {
        if ($billPayment->status === BillPayment::STATUS_REVERSED) {
            return back()->with('warning', 'A reversed bill payment cannot be updated.');
        }

        if ($billPayment->customer_id) {
            $billPayment->loadMissing('customer');
            abort_unless($billPayment->customer?->isVisibleTo($request->user()), 403, 'This member is assigned to another account officer.');
        }

        $validated = $request->validate([
            'status' => ['required', Rule::in([
                BillPayment::STATUS_PENDING,
                BillPayment::STATUS_SUCCESSFUL,
                BillPayment::STATUS_FAILED,
            ])],
            'provider_reference' => ['nullable', 'string', 'max:255'],
            'token' => ['nullable', 'string', 'max:2000'],
        ]);

        $billPayment->update([
            'status' => $validated['status'],
            'provider_reference' => $validated['provider_reference'] ?: $billPayment->provider_reference,
            'token' => $validated['token'] ?: $billPayment->token,
            'processed_at' => in_array($validated['status'], [BillPayment::STATUS_SUCCESSFUL, BillPayment::STATUS_FAILED], true)
                ? now()
                : $billPayment->processed_at,
        ]);

        ActivityLogger::log('bill_payment_status_updated', 'Bill payment '.$billPayment->reference.' status changed to '.$validated['status'].'.', $billPayment);

        return back()->with('status', 'Bill payment status updated.');
    }

    public function requery(Request $request, BillPayment $billPayment, BillPaymentService $billPayments): RedirectResponse
    {
        if ($billPayment->customer_id) {
            $billPayment->loadMissing('customer');
            abort_unless($billPayment->customer?->isVisibleTo($request->user()), 403, 'This member is assigned to another account officer.');
        }

        try {
            $updated = $billPayments->requeryProvider($billPayment);
        } catch (InvalidArgumentException $exception) {
            return back()->with('warning', $exception->getMessage());
        }

        return back()->with('status', 'Provider status checked. Current status: '.ucfirst($updated->status).'.');
    }

    public function cancel(Request $request, BillPayment $billPayment, TransactionCancellationService $cancellations): RedirectResponse
    {
        if ($billPayment->customer_id) {
            $billPayment->loadMissing('customer');
            abort_unless($billPayment->customer?->isVisibleTo($request->user()), 403, 'This member is assigned to another account officer.');
        }

        $validated = $request->validate([
            'canceled_reason' => ['required', 'string', 'max:1000'],
        ]);

        try {
            $cancellations->cancelBillPayment($billPayment, $validated['canceled_reason'], $request->user()->id);
        } catch (InvalidArgumentException $exception) {
            return back()->with('warning', $exception->getMessage());
        }

        return back()->with('status', 'Bill payment canceled and marked as reversed.');
    }

    private function serviceTypes(): array
    {
        return [
            'airtime' => 'Airtime',
            'data' => 'Data Bundle',
            'electricity' => 'Electricity / NEPA',
            'cable_tv' => 'Cable TV',
            'internet' => 'Internet',
        ];
    }

    private function providers(): array
    {
        return [
            'airtime' => ['MTN', 'Airtel', 'Glo', '9mobile'],
            'data' => ['MTN Data', 'Airtel Data', 'Glo Data', '9mobile Data'],
            'electricity' => ['Ikeja Electric', 'Eko Electric', 'Abuja Electric', 'Port Harcourt Electric', 'Kano Electric', 'Enugu Electric'],
            'cable_tv' => ['DSTV', 'GOTV', 'Startimes'],
            'internet' => ['Smile', 'Spectranet', 'Swift', 'Tizeti'],
        ];
    }

    private function statuses(): array
    {
        return [
            BillPayment::STATUS_PENDING => 'Pending',
            BillPayment::STATUS_SUCCESSFUL => 'Successful',
            BillPayment::STATUS_FAILED => 'Failed',
            BillPayment::STATUS_REVERSED => 'Reversed',
        ];
    }

    private function paymentMethods(): array
    {
        return [
            'cash' => 'Cash',
            'transfer' => 'Transfer',
            'pos' => 'POS',
            'wallet' => 'Wallet',
        ];
    }
}
