<?php

namespace App\Http\Livewire;

use App\Http\Controllers\TransactionController;
use App\Mail\TransferReceived;
use App\Models\Account;
use App\Models\Transaction;
use App\Models\TransactionType;
use App\Models\User;
use Cog\Laravel\Love\ReactionType\Models\ReactionType as LoveReactionType;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Livewire\Component;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;
use Namu\WireChat\Events\NotifyParticipant;
use Stevebauman\Location\Facades\Location as IpLocation;
use WireUi\Traits\WireUiActions;


use function Laravel\Prompts\error;

class Pay extends Component
{
    use WireUiActions;
    use \App\Traits\ProfilePermissionTrait;

    public $hours;
    public $minutes;
    public $amount;
    public $fromAccountId;
    public $fromAccountName;
    public $fromAccountBalance;
    public $toAccountId;
    public $toAccountName;
    public $toHolderId;
    public $toHolderType;
    public $toHolderName;
    public $toHolderPhoto;
    public $type;
    public $typeOptions = [];
    public $description;
    public $transactionTypeSelected;
    public $limitError;
    public $requiredError = false;
    public $submitEnabled = false;
    public $modalVisible = false;
    public $modalErrorVisible = false;
    public $rememberPaymentData = false;
    public $transactionTypeRemembered;

    public $typeOptionsProtected;

    protected $listeners = [
        'amount' => 'amountValidation',
        'fromAccountId',
        'toAccountId',
        'toAccountDetails' => 'toAccountDispatched',
        'description',
        'transactionTypeSelected',
        'resetForm',
        'removeSelectedAccount',
    ];

    protected function rules()
    {
        return [
            'amount' => timebank_config('payment.amount_rule'),
            'fromAccountId' => 'nullable|integer|exists:accounts,id',
            'toAccountId' => 'required|integer',
            'description' => timebank_config('payment.description_rule'),
            'transactionTypeSelected.name' => 'required|string|exists:transaction_types,name',
        ];
    }

    protected function messages()
    {
        return [
            'transactionTypeSelected.name.required' => __('messages.Transaction type is required'),
        ];
    }

    public function mount($amount = null, $hours = null, $minutes = null)
    {
        $this->modalVisible = false;

        $profile = getActiveProfile();

        if (!$profile) {
            abort(403, 'No active profile');
        }

        // CRITICAL SECURITY: Validate user has ownership/access to this profile
        // This prevents unauthorized access to the payment form via session manipulation
        \App\Helpers\ProfileAuthorizationHelper::authorize($profile);

        if ($amount !== null && is_numeric($amount) && $amount > 0) {
            $this->amount = $amount;
        } else {
            $hours = is_numeric($this->hours) ? (int) $this->hours : 0;
            $minutes = is_numeric($this->minutes) ? (int) $this->minutes : 0;
            $this->amount = $hours * 60 + $minutes;
        }
    }

    /**
     * Clear remembered transaction type when checkbox is unchecked
     */
    public function updatedRememberPaymentData($value)
    {
        if (!$value) {
            $this->transactionTypeRemembered = null;
        }
    }

    /**
     * Extra validation when amount looses focus
     *
     * @param  mixed $toAccountId
     * @return void
     */
    public function amountValidation($amount = null)
    {
        $this->amount = $amount ?? $this->amount;
        $this->validateOnly('amount');
    }

    /**
     * Sets fromAccountId after From Account drop down is selected
     *
     * @param  mixed $toAccount
     * @return void
     */
    public function fromAccountId($selectedAccount)
    {
        $this->modalVisible = false;

        // Handle case where no account is selected (e.g., Admin profiles)
        if (!$selectedAccount || !isset($selectedAccount['id'])) {
            $this->fromAccountId = null;
            $this->fromAccountName = null;
            $this->fromAccountBalance = null;
            return;
        }

        $this->fromAccountId = $selectedAccount['id'];
        $this->fromAccountName = $selectedAccount['name'];
        $this->fromAccountBalance = $selectedAccount['balance'];
        $this->validateOnly('fromAccountId');
        if ($this->fromAccountId == $this->toAccountId) {
            $this->dispatch('resetForm')->to(ToAccount::class);
            $this->dispatch('fromAccountId', $this->fromAccountId)->to(ToAccount::class);
        }
    }

    /**
     * Sets fromAccountId after To Account drop down is selected
     *
     * @param  mixed $toAccount
     * @return void
     */
    public function toAccountId($toAccountId)
    {
        $this->modalVisible = false;
        $this->toAccountId = $toAccountId;
        $this->validateOnly('toAccountId');
    }

    /**
     * Sets To account details after it is selected
     *
     * @param  mixed $details
     * @return void
     */
    public function toAccountDispatched($details)
    {
        if ($details) {
            // Check if we have a to account
            $this->requiredError = false;
            $this->toAccountId = $details['accountId'];
            $this->toAccountName = __(ucfirst(strtolower($details['accountName'])));
            $this->toHolderId = $details['holderId'];
            $this->toHolderType = $details['holderType'];
            $this->toHolderName = $details['holderName'];
            $this->toHolderPhoto = url($details['holderPhoto']);

            // Look up in config what transaction types are possible / allowed
            $canReceive = timebank_config('accounts.' . strtolower(class_basename($details['holderType'])) . '.receiving_types');
            $canPayConfig = timebank_config('permissions.' . strtolower(class_basename(session('activeProfileType'))) . '.payment_types');
            $canPay = $canPayConfig ?? [];
            $allowedTypes = array_intersect($canPay, $canReceive);

            // Check if this is an internal transfer (same accountable holder)
            $isInternalTransfer = (
                session('activeProfileType') === $details['holderType'] &&
                session('activeProfileId') == $details['holderId']
            );

            // If it's an internal transfer, only allow type 6 (Migration)
            if ($isInternalTransfer && !in_array(6, $allowedTypes)) {
                $allowedTypes = [6];
            }

            $this->typeOptionsProtected = $allowedTypes;
            $this->typeOptions = $this->typeOptionsProtected;

            // Pass remembered transaction type to the dispatch if remembering payment data
            $rememberedType = ($this->rememberPaymentData && $this->transactionTypeRemembered)
                ? $this->transactionTypeRemembered
                : null;
            $this->dispatch('transactionTypeOptions', $this->typeOptions, $rememberedType);
        } else {
            // if no to account is present, set id to null and validate so the user received an error
            $this->typeOptions = null;
            $this->dispatch('transactionTypeOptions', $this->typeOptions, null);
            $this->toAccountId = null;
        }
        $this->validateOnly('toAccountId');
    }

    /**
     * Sets description after it is updated
     *
     * @param  mixed $content
     * @return void
     */
    public function description($description)
    {
        $this->description = $description;
        $this->validateOnly('description');
    }

    /**
     * Sets transactionTypeSelected after it is updated
     *
     * @param  mixed $content
     * @return void
     */
    public function transactionTypeSelected($selected)
    {
        $this->transactionTypeSelected = $selected;
        $this->validateOnly('transactionTypeSelected');
    }


    public function showModal()
    {
        try {
            $this->validate();
        } catch (\Illuminate\Validation\ValidationException $errors) {
            // dump($errors);  //TODO! Replace dump and render error message nicely for user
            $this->validate();
            // Execution stops here if validation fails.
        }

        $fromAccountId = $this->fromAccountId;
        $toAccountId = $this->toAccountId;
        $amount = $this->amount;

        // Check if fromAccountId is null (e.g., Admin profiles without accounts)
        if (!$fromAccountId) {
            $this->notification()->error(
                __('No account available'),
                __('Your profile does not have any accounts to make payments from.')
            );
            return;
        }

        $transactionController = new TransactionController();
        $balanceFrom = $transactionController->getBalance($fromAccountId);
        $balanceTo = $transactionController->getBalance($toAccountId);

        if ($toAccountId === $fromAccountId) {
            return redirect()->back()->with('error', 'You cannot transfer Hours from and to the same account');
        } else {
            $fromAccountExists = Account::where('id', $toAccountId)->first();
            if (!$fromAccountExists) {
                return redirect()->back()->with('error', 'Account not found.');
            } else {
                $transferToAccount = $fromAccountExists->id;
            }

            $f = Account::where('id', $fromAccountId)->select('limit_min')->first();
            $limitMinFrom = $f->limit_min;
            $t = Account::where('id', $transferToAccount)->select('limit_max', 'limit_min')->first();
            $limitMaxTo = $t->limit_max - $t->limit_min;

            $transferBudgetFrom = $balanceFrom - $limitMinFrom;
            if (timebank_config('account_info.' . strtolower(class_basename($this->toHolderType)) . '.balance_public')) {
                $transferBudgetTo = $limitMaxTo - $balanceTo;
                $balanceToPublic = true;
            } else {
                $transferBudgetTo = $limitMaxTo - $balanceTo;
                $balanceToPublic = false;
            }

            $this->checkBalanceLimits($amount, $transferBudgetTo, $transferBudgetFrom, $limitMinFrom, $balanceToPublic);

            $this->modalVisible = true;
        }
    }

    /**
     * Create transfer, output success / error message and reset from.
     *
     * @return void
     */
    public function doTransfer()
    {
        $fromAccountId = $this->fromAccountId;
        $toAccountId = $this->toAccountId;
        $amount = $this->amount;
        $description = $this->description;
        $transactionTypeId = $this->transactionTypeSelected['id'];

        // Block payment if the active user only has coordinator role (no payment rights)
        if (!$this->getCanCreatePayments()) {
            $warningMessage = 'Unauthorized payment attempt: coordinator role has no payment rights';
            return $this->logAndReport($warningMessage, $fromAccountId, $toAccountId, $amount, $description);
        }

        // NOTICE: Livewire public properties can be changed / hacked on the client side!
        // Check therefore check again ownership of the fromAccountId.
        // The getAccountsInfo() from the AccountInfoTrait checks the active profile sessions.
        $transactionController = new TransactionController();
        $accountsInfo = collect($transactionController->getAccountsInfo());
        // Check if the session's active profile owns the submitted fromAccountId
        if (!$accountsInfo->contains('id', $fromAccountId)) {
            $warningMessage = 'Unauthorized payment attempt: illegal access of From account';
            return $this->logAndReport($warningMessage, $fromAccountId, $toAccountId, $amount, $description);
        }

        // Check if From and To Account is different
        if ($toAccountId === $fromAccountId) {
            $warningMessage = 'Impossible payment attempt: To and From account are the same';
            return $this->logAndReport($warningMessage, $fromAccountId, $toAccountId, $amount, $description);
        }

        // Check if the To Account exists and is not removed
        $toAccountExists = Account::where('id', $toAccountId)->notRemoved()->first();
        if (!$toAccountExists) {
            $warningMessage = 'Impossible payment attempt: To account not found';
            return $this->logAndReport($warningMessage, $fromAccountId, $toAccountId, $amount, $description);
        }
        $transferToAccount = $toAccountExists->id;

        // Check if the To Accountable exists and is not removed
        $toAccountableExists = Account::find($toAccountId)->accountable()->notRemoved()->first();
        if (!$toAccountableExists) {
            $warningMessage = 'Impossible payment attempt:  To account holder not found';
            return $this->logAndReport($warningMessage, $fromAccountId, $toAccountId, $amount, $description);
        }

        // Check if the From Account exists and is not removed
        $fromAccountExists = Account::where('id', $fromAccountId)->notRemoved()->first();
        if (!$fromAccountExists) {
            $warningMessage = 'Impossible payment attempt: From account not found';
            return $this->logAndReport($warningMessage, $fromAccountId, $toAccountId, $amount, $description);
        }

        // Check if the From Accountable exists and is not removed
        $fromAccountableExists = Account::find($fromAccountId)->accountable()->notRemoved()->first();
        if (!$fromAccountableExists) {
            $warningMessage = 'Impossible payment attempt:  From account holder not found';
            return $this->logAndReport($warningMessage, $fromAccountId, $toAccountId, $amount, $description);
        }


        // Check if the transaction type is allowed, with an exception for internal transfers of type 6 (Migration)
        $fromAccount = Account::find($fromAccountId);
        $isInternalTransferType = (
            $fromAccount->accountable_type === $fromAccountExists->accountable_type &&
            $fromAccount->accountable_id === $fromAccountExists->accountable_id &&
            $transactionTypeId == 6
        );

        // Check if the To transactionTypeSelected is allowed, unless it's a specific internal transfer
        if (!$isInternalTransferType && !in_array($transactionTypeId, $this->typeOptionsProtected)) {
            $transactionType = TransactionType::find($transactionTypeId)->name ?? 'id: '. $transactionTypeId;
            $warningMessage = 'Impossible payment attempt: transaction type not allowed';
            return $this->logAndReport($warningMessage, $fromAccountId, $toAccountId, $amount, $description, $transactionType);
        }

        $f = Account::where('id', $fromAccountId)->select('limit_min')->first();
        $limitMinFrom = $f->limit_min;
        $t = Account::where('id', $transferToAccount)->select('limit_max', 'limit_min')->first();
        $limitMaxTo = $t->limit_max - $t->limit_min;

        $balanceFrom = $transactionController->getBalance($fromAccountId);
        $balanceTo = $transactionController->getBalance($toAccountId);

        $transferBudgetFrom = $balanceFrom - $limitMinFrom;
        if (timebank_config('account_info.' . strtolower(class_basename($this->toHolderType)) . '.balance_public')) {
            $transferBudgetTo = $limitMaxTo - $balanceTo;
            $balanceToPublic = true;
        } else {
            $transferBudgetTo = $limitMaxTo - $balanceTo;
            $balanceToPublic = false;
        }

        // Check balance limits
        $this->checkBalanceLimits($amount, $transferBudgetTo, $transferBudgetFrom, $limitMinFrom, $balanceToPublic);

        // Use a database transaction for saving the payment
        DB::beginTransaction();
        try {

            $transfer = new Transaction();
            $transfer->from_account_id = $fromAccountId;
            $transfer->to_account_id = $transferToAccount;
            $transfer->amount = $amount;
            $transfer->description = $description;
            $transfer->transaction_type_id = $transactionTypeId;
            $transfer->creator_user_id = Auth::user()->id;
            $save = $transfer->save();

            // TODO: remove testing comment for production
            // Uncomment to test a failed transaction
            //$save = false;

            if ($save) {

                // Commit the database transaction
                DB::commit();
                // WireUI notification
                $this->notification()->success(
                    __('Transaction done!'),
                    __('messages.payment.success', [
                        'amount' => tbFormat($amount),
                        'account_name' => $this->toAccountName,
                        'holder_name' => $this->toHolderName,
                        'transaction_url' => route('transaction.show', ['transactionId' => $transfer->id, 'qrModalVisible' => true]),
                        'transaction_id' => $transfer->id,
                    ])
                );

                // Store transaction type if remembering payment data
                if ($this->rememberPaymentData) {
                    $this->transactionTypeRemembered = $this->transactionTypeSelected;
                }

                // Conditionally reset form based on rememberPaymentData setting
                if ($this->rememberPaymentData) {
                    // Only reset to-account related fields, keeping amount and description
                    $this->dispatch('resetForm')->to(ToAccount::class);
                    $this->toAccountId = null;
                    $this->toAccountName = null;
                    $this->toHolderId = null;
                    $this->toHolderType = null;
                    $this->toHolderName = null;
                    $this->toHolderPhoto = null;
                    $this->transactionTypeSelected = null;
                    $this->modalVisible = false;
                } else {
                    // Reset all fields including remembered transaction type
                    $this->transactionTypeRemembered = null;
                    $this->dispatch('resetForm');
                }

                // Send chat message and an email if conditions are met
                $recipient = $transfer->accountTo->accountable;
                $sender = $transfer->accountFrom->accountable;
                $messageLocale = $recipient->lang_preference ?? $sender->lang_preference;
                if (!Lang::has('messages.pay_chat_message', $messageLocale)) {  // Check if the translation key exists for the selected locale
                    $messageLocale = config('app.fallback_locale'); // Fallback to the app's default locale
                }

                $chatMessage = __('messages.pay_chat_message', [
                    'amount' => tbFormat($amount),
                    'account_name' => $this->toAccountName,
                ], $messageLocale);

                $chatTransactionStatement = LaravelLocalization::getURLFromRouteNameTranslated($messageLocale, 'routes.statement', array('transactionId' => $transfer->id));

                // Send Wirechat message
                $message = $sender->sendMessageTo($recipient, $chatMessage);
                $message = $sender->sendMessageTo($recipient, $chatTransactionStatement);

                // Broadcast the NotifyParticipant event to wirechat messenger
                broadcast(new NotifyParticipant($recipient, $message));

                // Check if the recipient has message settings for receiving this email and has also an email address
                if (isset($recipient->email)) {
                    $messageSettings = method_exists($recipient, 'message_settings') ? $recipient->message_settings()->first() : null;

                    // Always send email unless payment_received is explicitly false
                    if (!$messageSettings || !($messageSettings->payment_received === false || $messageSettings->payment_received === 0)) {
                        $now = now();
                        Mail::to($recipient->email)->later($now->addSeconds(5), new TransferReceived($transfer, $messageLocale));
                    }
                }

                // Add Love Reaction with transaction type name on both models
                $reactionType = TransactionType::find($transactionTypeId)->name;
                try {
                    $reacterFacadeSender = $sender->viaLoveReacter()->reactTo($recipient, $reactionType);
                    $reacterFacadeRecipient = $recipient->viaLoveReacter()->reactTo($sender, $reactionType);
                } catch (\Exception $e) {
                    // Ignore if reaction type does not exist
                }

            } else {
                throw new \Exception('Transaction could not be saved');

            }
        } catch (\Exception $e) {
            DB::rollBack();

            // WireUI notification
            $this->notification()->send([
                'title' => __('Transaction failed!'),
                'description' => __('messages.payment.failed_description', [
                    'error' => $e->getMessage(),
                ]),
                'icon' => 'error',
                'timeout' => 50000
            ]);

            $warningMessage = 'Transaction failed';
            $this->logAndReport($warningMessage, $fromAccountId, $toAccountId, $amount, $description, '', $e);

            $this->resetForm();

            return back();
        }
    }


    /**
     * Check balance limits for a transfer operation.
     *
     * This method checks if the transfer amount exceeds the allowed budget limits
     * for both the source and destination accounts. It sets an appropriate error
     * message and makes the error modal visible if any limit is exceeded.
     */
    private function checkBalanceLimits($amount, $transferBudgetTo, $transferBudgetFrom, $limitMinFrom, $balanceToPublic)
    {
        if ($amount > $transferBudgetFrom && $amount > $transferBudgetTo && $transferBudgetFrom <= $transferBudgetTo) {
            $this->limitError = __('messages.pay_limit_error_budget_from', [
                        'limitMinFrom' => tbFormat($limitMinFrom),
                        'transferBudgetFrom' => tbFormat($transferBudgetFrom),
                    ]);
            return $this->modalErrorVisible = true;
        }
        if ($amount > $transferBudgetFrom && $amount > $transferBudgetTo && $transferBudgetFrom > $transferBudgetTo) {
            if ($balanceToPublic) {
                $this->limitError = __('messages.pay_limit_error_budget_from_and_to', [
                        'limitMinFrom' => tbFormat($limitMinFrom),
                        'transferBudgetTo' => tbFormat($transferBudgetTo),
                    ]);
            } else {
                $this->limitError = __('messages.pay_limit_error_budget_from_and_to_without_budget_to', [
                                'limitMinFrom' => tbFormat($limitMinFrom),
                            ]);
            }
            return $this->modalErrorVisible = true;
        }
        if ($amount > $transferBudgetFrom) {
            $this->limitError = __('messages.pay_limit_error_budget_from', [
                        'limitMinFrom' => tbFormat($limitMinFrom),
                        'transferBudgetFrom' => tbFormat($transferBudgetFrom),
                    ]);
            return $this->modalErrorVisible = true;
        }
        if ($amount > $transferBudgetTo) {
            if ($balanceToPublic) {
                $this->limitError = __('messages.pay_limit_error_budget_to', [
                    'transferBudgetTo' => tbFormat($transferBudgetTo),
                ]);
            } else {
                $this->limitError = __('messages.pay_limit_error_budget_to_without_budget_to', [
                    'transferBudgetTo' => tbFormat($transferBudgetTo),
                    'toHolderName' => $this->toHolderName,
                ]);
            }
            return $this->modalErrorVisible = true;
        }
        $this->limitError = null;
    }


    /**
     * Logs a warning message and reports it via email to the system administrator.
     *
     * This method logs a warning message with detailed information about the event,
     * including account details, user details, IP address, and location. It also
     * sends an email to the system administrator with the same information.
     */
    private function logAndReport($warningMessage, $fromAccountId, $toAccountId, $amount, $description, $transactionType = '', $error = '')
    {
        $ip = request()->ip();
        $ipLocationInfo = IpLocation::get($ip);
        // Escape ipLocation errors when not in production
        if (!$ipLocationInfo || App::environment(['local', 'development', 'staging'])) {
            $ipLocationInfo = (object) [
                'cityName' => 'local City',
                'regionName' => 'local Region',
                'countryName' => 'local Country',
            ];
        }
        $eventTime = now()->toDateTimeString();

        // Log this event and mail to admin
        $fromAccountInfo = $fromAccountId ? Account::find($fromAccountId)?->accountable()?->value('name') : 'N/A';
        $toAccountInfo = $toAccountId ? Account::find($toAccountId)?->accountable()?->value('name') : 'N/A';

        Log::warning($warningMessage, [
            'fromAccountId' => $fromAccountId ?? 'N/A',
            'fromAccountHolder' => $fromAccountInfo,
            'toAccountId' => $toAccountId ?? 'N/A',
            'toAccountHolder' => $toAccountInfo,
            'amount' => $amount,
            'description' => $description,
            'userId' => Auth::id(),
            'userName' => Auth::user()->name,
            'activeProfileId' => session('activeProfileId'),
            'activeProfileType' => session('activeProfileType'),
            'activeProfileName' => session('activeProfileName'),
            'transactionType' => ucfirst($transactionType),
            'IP address' => $ip,
            'IP location' => $ipLocationInfo->cityName . ', ' . $ipLocationInfo->regionName . ', ' . $ipLocationInfo->countryName,
            'Event Time' => $eventTime,
            'Message' => $error,
        ]);
        Mail::raw(
            $warningMessage . '.' . "\n\n" .
            'From Account ID: ' . ($fromAccountId ?? 'N/A') . "\n" .
            'From Account Holder: ' . $fromAccountInfo . "\n" .
            'To Account ID: ' . ($toAccountId ?? 'N/A') . "\n" .
            'To Account Holder: ' . $toAccountInfo . "\n" .
            'Amount: ' . $amount . "\n" .
            'Description: ' . $description . "\n" .
            'User ID: ' . Auth::id() . "\n" . 'User Name: ' . Auth::user()->name . "\n" .
            'Active Profile ID: ' . session('activeProfileId') . "\n" .
            'Active Profile Type: ' . session('activeProfileType') . "\n" .
            'Active Profile Name: ' . session('activeProfileName') . "\n" .
            'Transaction Type: ' . ucfirst($transactionType) . "\n" .
            'IP address: ' . $ip . "\n" .
            'IP location: ' . $ipLocationInfo->cityName . ', ' . $ipLocationInfo->regionName . ', ' . $ipLocationInfo->countryName . "\n" .
            'Event Time: ' . $eventTime . "\n\n" .
            $error,
            function ($message) use ($warningMessage) {
                $message->to(timebank_config('mail.system_admin.email'))->subject($warningMessage);
            },
        );

        return redirect()
            ->back()
            ->with('error', __($warningMessage) . '. ' . __('This event has been logged and reported to our system administrator') . '.');
    }


    public function resetForm()
    {
        // Always reset the to account and transaction type
        $this->toAccountId = null;
        $this->toAccountName = null;
        $this->transactionTypeSelected = null;

        // Only reset amount, description, and remembered type if not remembering payment data
        if (!$this->rememberPaymentData) {
            $this->amount = null;
            $this->description = null;
            $this->transactionTypeRemembered = null;
        }

        $this->modalVisible = false;
    }

    public function removeSelectedAccount()
    {
        $this->toAccountId = null;
        $this->toAccountName = null;
        $this->toHolderId = null;
        $this->toHolderName = null;
    }

    /**
     * Render the livewire component
     *
     * @return void
     */
    public function render()
    {
        return view('livewire.pay');
    }
}
