<?php

/**
 * Invoice Ninja (https://invoiceninja.com).
 *
 * @link https://github.com/invoiceninja/invoiceninja source repository
 *
 * @copyright Copyright (c) 2026. Invoice Ninja LLC (https://invoiceninja.com)
 *
 * @license https://www.elastic.co/licensing/elastic-license
 */

namespace App\Livewire;

use App\Utils\Number;
use App\Models\Client;
use App\Models\Invoice;
use Livewire\Component;
use App\Libraries\MultiDB;
use Illuminate\Support\Str;
use App\Models\Subscription;
use App\Models\ClientContact;
use InvalidArgumentException;
use App\Factory\ClientFactory;
use App\Utils\Traits\MakesHash;
use App\Models\RecurringInvoice;
use App\Jobs\Mail\NinjaMailerJob;
use Livewire\Attributes\Computed;
use App\DataMapper\ClientSettings;
use App\Mail\Subscription\OtpCode;
use App\Jobs\Mail\NinjaMailerObject;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use App\Repositories\ClientRepository;
use App\Repositories\ClientContactRepository;
use Laracasts\Presenter\Exceptions\PresenterException;
use Livewire\Attributes\Locked;

class BillingPortalPurchasev2 extends Component
{
    use MakesHash;
    /**
     * Random hash generated by backend to handle the tracking of state.
     *
     * @var string
     */
    public $hash;

    /**
     * E-mail address model for user input.
     *
     * @var string
     */
    public $email;

    #[Locked]
    public $subscription_id;


    /**
     * Id for CompanyGateway record.
     *
     * @var string|integer
     */
    #[Locked]
    public $company_gateway_id;

    /**
     * Id for GatewayType.
     *
     * @var string|integer
     */
    #[Locked]
    public $payment_method_id;

    /**
     * Array of front end variables for
     * the subscription
     */
    public $data = [];

    public $price;

    /**
     * List of payment methods fetched from client.
     *
     * @var array
     */
    public $methods = [];
    /**
     * Coupon model for user input
     *
     * @var string
     */
    public $coupon;

    /**
     * Quantity for seats
     *
     * @var int
     */
    public $quantity;

    public $invoice_hashed_id = '';

    public $payable_amount = 0;
    /**
     * First-hit request data (queries, locales...).
     *
     * @var array
     */
    public $request_data = [];

    /**
     * Instance of company.
     *
     * @var string
     */
    #[Locked]
    public string $db;

    /**
     * Campaign reference.
     *
     * @var string|null
     */
    public $campaign;

    public $bundle;
    public $recurring_products;
    public $products;
    public $optional_recurring_products;
    public $optional_products;
    public $total;
    public $non_recurring_total;
    public $recurring_total;
    public $discount;
    public $sub_total;
    public $authenticated = false;
    public $login;
    public $float_amount_total;
    public $payment_started = false;
    public $valid_coupon = false;
    public $payable_invoices = [];
    public $payment_confirmed = false;
    public $is_eligible = true;
    public $not_eligible_message = '';
    public $check_rff = false;

    public ?string $contact_first_name;
    public ?string $contact_last_name;
    public ?string $contact_email;
    public ?string $client_city;
    public ?string $client_postal_code;

    #[Computed]
    public function subscription()
    {
        return Subscription::query()->with('company')->find($this->subscription_id);
    }

    #[Computed]
    public function contact()
    {
        return auth()->guard('contact')->user();
    }

    public function mount()
    {
        MultiDB::setDb($this->db);

        /** @var \App\Models\Subscription $subscription */
        $subscription = $this->subscription();

        if (!$subscription) {

            session()->flash('title', __('texts.subscription_unavailable'));
            session()->flash('notification', '');

            return redirect()->route('client.error');

        }


        if (auth()->guard('contact')->check()) {
            $this->email = auth()->guard('contact')->user()->email;
            $this->authenticated = true;
            $this->payment_started = true;
        } else {
            $this->bundle = collect();
        }

        $this->discount = 0;
        $this->sub_total = 0;
        $this->float_amount_total = 0;

        $this->invoice_hashed_id = '';
        $this->payable_amount = 0;

        $this->data = [];

        $this->price = $subscription->price; // ?
        $this->float_amount_total = $this->price;

        $this->products = $subscription->service()->products();
        $this->recurring_products = $subscription->service()->recurring_products();
        $this->optional_recurring_products = $subscription->service()->optional_recurring_products();
        $this->optional_products = $subscription->service()->optional_products();

        $this->bundle = collect();

        //every thing below is redundant

        if (request()->query('coupon')) {
            $this->coupon = request()->query('coupon');
            $this->handleCoupon();
        } elseif (isset($subscription->promo_code) && strlen($subscription->promo_code ?? '') == 0 && $subscription->promo_discount > 0) {
            $this->price = $subscription->promo_price;
        }
    }

    public function loginValidation()
    {
        $this->resetErrorBag('login');
        $this->resetValidation('login');
    }

    public function handleLogin($user_code)
    {
        $this->resetErrorBag('login');
        $this->resetValidation('login');

        $subscription = $this->subscription();

        $code = Cache::get("subscriptions:otp:{$this->email}");

        if ($user_code != $code) {
            $errors = $this->getErrorBag();
            $errors->add('login', ctrans('texts.invalid_code'));
            return $this;
        }

        $contact = ClientContact::where('email', $this->email)
                                ->where('company_id', $subscription->company_id)
                                ->first();

        if ($contact) {
            Auth::guard('contact')->loginUsingId($contact->id, true);
            $this->dispatch('update-csrf', token: csrf_token());
        } else {
            $this->createBlankClient();
        }

        $this->getPaymentMethods();

        $this->authenticated = true;
        $this->payment_started = true;
    }

    public function resetEmail()
    {
        $this->resetErrorBag('login');
        $this->resetValidation('login');
        $this->email = null;
    }

    public function handleEmail()
    {
        $this->validateOnly('email', ['email' => 'required|bail|email:rfc']);

        $rand = rand(100000, 999999);

        $email_hash = "subscriptions:otp:{$this->email}";

        Cache::put($email_hash, $rand, 120);

        $this->emailOtpCode($rand);
    }

    private function emailOtpCode($code)
    {
        $subscription = $this->subscription();

        $cc = new ClientContact();
        $cc->email = $this->email;

        $nmo = new NinjaMailerObject();
        $nmo->mailable = new OtpCode($subscription->company, $cc, $code);
        $nmo->company = $subscription->company;
        $nmo->settings = $subscription->company->settings;
        $nmo->to_user = $cc;
        NinjaMailerJob::dispatch($nmo);
    }

    /**
     * Handle a coupon being entered into the checkout
     */
    public function handleCoupon()
    {

        $subscription = $this->subscription();

        $this->resetErrorBag('coupon');
        $this->resetValidation('coupon');

        if ($this->coupon == $subscription->promo_code) {
            $this->valid_coupon = true;
            $this->buildBundle();
        } else {
            $this->discount = 0;
            $this->valid_coupon = false;
            $this->buildBundle();
            $errors = $this->getErrorBag();
            $errors->add('coupon', ctrans('texts.invalid_coupon'));
            return $this;
        }
    }

    /**
     * Build the bundle in the checkout
     */
    public function buildBundle()
    {
        $this->bundle = collect();
        $subscription = $this->subscription();

        $data = $this->data;

        /* Recurring products can have a variable quantity */
        foreach ($this->recurring_products as $key => $p) {
            $qty = $data[$key]['recurring_qty'] ?? 1;
            $total = $p->price * $qty;

            $this->bundle->push([
                'description' => $p->notes,
                'product_key' => $p->product_key,
                'unit_cost' => $p->price,
                'product' => substr(strip_tags($p->markdownNotes()), 0, 50),
                'price' => Number::formatMoney($total, $subscription->company) . ' / ' . RecurringInvoice::frequencyForKey($subscription->frequency_id),
                'total' => $total,
                'qty' => $qty,
                'is_recurring' => true,
                'product_image' => $p->product_image,
            ]);
        }

        /* One time products can only have a single quantity */
        foreach ($this->products as $key => $p) {
            $qty = 1;
            $total = $p->price * $qty;

            $this->bundle->push([
                'description' => $p->notes,
                'product_key' => $p->product_key,
                'unit_cost' => $p->price,
                'product' => substr(strip_tags($p->markdownNotes()), 0, 50),
                'price' => Number::formatMoney($total, $subscription->company),
                'total' => $total,
                'qty' => $qty,
                'is_recurring' => false,
            ]);
        }

        foreach ($this->data as $key => $value) {
            /* Optional recurring products can have a variable quantity */
            if (isset($this->data[$key]['optional_recurring_qty'])) {
                $p = $this->optional_recurring_products->first(function ($v, $k) use ($key) {
                    return $k == $key;
                });

                $qty = $this->data[$key]['optional_recurring_qty'] ?? false;
                $total = $p->price * $qty;

                if ($qty) {
                    $this->bundle->push([
                        'description' => $p->notes,
                        'product_key' => $p->product_key,
                        'unit_cost' => $p->price,
                        'product' => substr(strip_tags($p->markdownNotes()), 0, 50),
                        'price' => Number::formatMoney($total, $subscription->company) . ' / ' . RecurringInvoice::frequencyForKey($subscription->frequency_id),
                        'total' => $total,
                        'qty' => $qty,
                        'is_recurring' => true,
                    ]);
                }
            }

            /* Optional products can have a variable quantity */
            if (isset($this->data[$key]['optional_qty'])) {
                $p = $this->optional_products->first(function ($v, $k) use ($key) {
                    return $k == $key;
                });

                $qty = $this->data[$key]['optional_qty'] ?? false;
                $total = $p->price * $qty;

                if ($qty) {
                    $this->bundle->push([
                        'description' => $p->notes,
                        'product_key' => $p->product_key,
                        'unit_cost' => $p->price,
                        'product' => substr(strip_tags($p->markdownNotes()), 0, 50),
                        'price' => Number::formatMoney($total, $subscription->company),
                        'total' => $total,
                        'qty' => $qty,
                        'is_recurring' => false,
                    ]);
                }
            }
        }

        $this->sub_total = Number::formatMoney($this->bundle->sum('total'), $subscription->company);
        $this->recurring_total = Number::formatMoney($this->bundle->where('is_recurring', true)->sum('total'), $subscription->company);
        $this->non_recurring_total = Number::formatMoney($this->bundle->where('is_recurring', false)->sum('total'), $subscription->company);
        $this->total = $this->sub_total;

        if ($this->valid_coupon) {
            if ($this->subscription()->is_amount_discount) {
                $discount = $subscription->promo_discount;
            } else {
                $discount = round($this->bundle->sum('total') * ($subscription->promo_discount / 100), 2);
            }

            $this->discount = Number::formatMoney($discount, $subscription->company);

            $this->total = Number::formatMoney(($this->bundle->sum('total') - $discount), $subscription->company);

            $this->float_amount_total = ($this->bundle->sum('total') - $discount);
        } else {
            $this->float_amount_total = $this->bundle->sum('total');
            $this->total = Number::formatMoney($this->float_amount_total, $subscription->company);
        }

        return $this;
    }

    /**
     * @param mixed $propertyName
     *
     * @return BillingPortalPurchasev2
     */
    public function updated($propertyName): self
    {
        if (in_array($propertyName, ['login','email'])) {
            return $this;
        }

        $this->buildBundle();

        return $this;
    }

    /**
     * Fetching payment methods from the client.
     *
     * @return $this
     */
    protected function getPaymentMethods(): self
    {
        $contact = $this->contact();

        if ($this->float_amount_total == 0) {
            $this->methods = [];
        }

        if ($contact && $this->float_amount_total >= 0) {
            $this->methods = $contact->client->service()->getPaymentMethods($this->float_amount_total);
        }

        foreach ($this->methods as $method) {

            if ($method['is_paypal'] == '1' && !$this->check_rff) {
                $this->rff();
                break;
            }

        }

        return $this;
    }

    protected function rff()
    {

        $contact = $this->contact();

        $this->contact_first_name = $contact->first_name;
        $this->contact_last_name = $contact->last_name;
        $this->contact_email = $contact->email;
        $this->client_city = $contact->client->city;
        $this->client_postal_code = $contact->client->postal_code;

        if (
            strlen($this->contact_first_name ?? '') == 0
            || strlen($this->contact_last_name ?? '') == 0
            || strlen($this->contact_email ?? '') == 0
            || strlen($this->client_city ?? '') == 0
            || strlen($this->client_postal_code ?? '') == 0
        ) {
            $this->check_rff = true;
        }

        return $this;
    }

    public function handleRff()
    {

        $validated = $this->validate([
            'contact_first_name' => ['required'],
            'contact_last_name' => ['required'],
            'client_city' => ['required'],
            'client_postal_code' => ['required'],
            'contact_email' => ['required', 'email'],
        ]);

        $contact = $this->contact();

        $this->check_rff = false;

        $contact->first_name = $validated['contact_first_name'];
        $contact->last_name = $validated['contact_last_name'];
        $contact->email = $validated['contact_email'];
        $contact->client->postal_code = $validated['client_postal_code'];
        $contact->client->city = $validated['client_city'];

        $contact->pushQuietly();

        $this->refreshComponent();

        return $this;
    }

    protected function refreshComponent()
    {
        $this->dispatch('$refresh');
    }

    /**
     * Middle method between selecting payment method &
     * submitting the from to the backend.
     *
     * @param $company_gateway_id
     * @param $gateway_type_id
     */
    public function handleMethodSelectingEvent($company_gateway_id, $gateway_type_id)
    {
        $this->payment_confirmed = true;

        $this->company_gateway_id = $company_gateway_id;
        $this->payment_method_id = $gateway_type_id;

        $this->handleBeforePaymentEvents();

        $this->dispatch('beforePaymentEventsCompleted');

    }

    /**
     * Method to handle events before payments.
     *
     * @return self
     */
    public function handleBeforePaymentEvents(): self
    {
        $subscription = $this->subscription();
        $contact = $this->contact();

        $eligibility_check = $subscription->service()->isEligible($contact);

        if (is_array($eligibility_check) && $eligibility_check['message'] != 'Success') {
            $this->is_eligible = false;
            $this->not_eligible_message = $eligibility_check['message'];

            return $this;
        }

        $data = [
            'client_id' => $contact->client->hashed_id,
            'date' => now()->format('Y-m-d'),
            'invitations' => [[
                'key' => '',
                'client_contact_id' => $contact->hashed_id,
            ]],
            'user_input_promo_code' => $this->coupon,
            'coupon' => empty($subscription->promo_code) ? '' : $this->coupon,
        ];

        $invoice = $this->subscription()
            ->service()
            ->createInvoiceV2($this->bundle, $contact->client_id, $this->valid_coupon)
            ->service()
            ->markSent()
            ->fillDefaults()
            ->adjustInventory()
            ->save();

        $this->payable_amount = $invoice->partial > 0 ? \App\Utils\Number::formatValue($invoice->partial, $invoice->client->currency()) : \App\Utils\Number::formatValue($invoice->balance, $invoice->client->currency());
        $this->invoice_hashed_id = $invoice->hashed_id;

        $context = 'purchase';

        if (config('ninja.ninja_default_company_id') == $this->subscription()->company_id && $this->subscription()->service()->recurring_products()->first()?->product_key == 'whitelabel') {
            $context = 'whitelabel';
        }

        if (config('ninja.ninja_default_company_id') == $this->subscription()->company_id && in_array($this->subscription()->service()->products()->first()?->product_key, ['peppol_500','peppol_1000','selfhost_peppol_500','selfhost_peppol_1000'])) {
            $context = $this->subscription()->service()->products()->first()?->product_key;
        }

        Cache::put($this->hash, [
            'subscription_id' => $this->subscription()->hashed_id,
            'email' => $this->email ?? $contact->email,
            'client_id' => $contact->client->hashed_id,
            'invoice_id' => $invoice->hashed_id,
            'context' => $context,
            'campaign' => $this->campaign,
            'bundle' => $this->bundle,
        ], now()->addMinutes(60));

        return $this;
    }


    /**
     * Starts the trial
     *
     * @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse
     */
    public function handleTrial()
    {
        $contact = $this->contact();

        return $this->subscription()->service()->startTrial([
            'email' => $this->email ?? $contact->email,
            'quantity' => $this->quantity,
            'contact_id' => $contact->hashed_id,
            'client_id' => $contact->client->hashed_id,
            'bundle' => $this->bundle,
        ]);
    }

    /**
     * When the subscription total comes to $0 we
     * pass back a $0 Invoice.
     *
     * @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse
     */
    public function handlePaymentNotRequired()
    {
        $subscription = $this->subscription();
        $contact = $this->contact();

        $eligibility_check = $subscription->service()->isEligible($contact);

        if (is_array($eligibility_check) && $eligibility_check['message'] != 'Success') {
            $this->is_eligible = false;
            $this->not_eligible_message = $eligibility_check['message'];
            return $this;
        }

        $invoice = $this->subscription()
            ->service()
            ->createInvoiceV2($this->bundle, $contact->client_id, $this->valid_coupon)
            ->service()
            ->fillDefaults()
            ->adjustInventory()
            ->save();

        $invoice->number = null;

        $invoice->service()
                ->markPaid()
                ->save();

        return $subscription
                    ->service()
                    ->handleNoPaymentFlow($invoice, $this->bundle, $contact);
    }












    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public function rules()
    {
        $rules = [
        ];

        return $rules;
    }

    public function attributes()
    {
        $attributes = [
        ];

        return $attributes;
    }

    public function store() {}

    /**
     * Create a blank client. Used for new customers purchasing.
     *
     * @return mixed
     * @throws \Laracasts\Presenter\Exceptions\PresenterException
     */
    protected function createBlankClient()
    {
        $subscription = $this->subscription();

        $company = $subscription->company;
        $user = $subscription->user;
        $user->setCompany($company);

        $client_repo = new ClientRepository(new ClientContactRepository());

        $data = [
            'name' => '',
            'contacts' => [
                ['email' => $this->email],
            ],
            'client_hash' => Str::random(40),
            'settings' => ClientSettings::defaults(),
        ];

        foreach ($this->request_data as $field => $value) {
            if (in_array($field, Client::$subscriptions_fillable)) {
                $data[$field] = $value;
            }

            if (in_array($field, ClientContact::$subscription_fillable)) {
                $data['contacts'][0][$field] = $value;
            }
        }

        if (array_key_exists('currency_id', $this->request_data)) {

            /** @var \Illuminate\Support\Collection<\App\Models\Currency> */
            $currencies = app('currencies');

            $currency = $currencies->first(function ($item) {
                return $item->id == $this->request_data['currency_id'];
            });

            if ($currency) {
                $data['settings']->currency_id = $currency->id;
            }
        } elseif ($subscription->group_settings && property_exists($subscription->group_settings->settings, 'currency_id')) {

            /** @var \Illuminate\Support\Collection<\App\Models\Currency> */
            $currencies = app('currencies');

            $currency = $currencies->first(function ($item) use ($subscription) {
                return $item->id == $subscription->group_settings->settings->currency_id;
            });

            if ($currency) {
                $data['settings']->currency_id = $currency->id;
            }
        } else {
            $data['settings']->currency_id = $subscription->company->getSetting('currency_id');
        }

        if (array_key_exists('locale', $this->request_data)) {
            $request = $this->request_data;

            /** @var \Illuminate\Support\Collection<\App\Models\Language> */
            $languages = app('languages');

            $record = $languages->first(function ($item) use ($request) {
                return $item->locale == $request['locale'];
            });

            if ($record) {
                $data['settings']['language_id'] = (string) $record->id;
            }
        }

        $client = $client_repo->save($data, ClientFactory::create($company->id, $user->id));
        $contact = $client->fresh()->contacts->first();

        Auth::guard('contact')->loginUsingId($contact->id, true);
        $this->dispatch('update-csrf', token: csrf_token());

        return $contact;
    }



    /**
     * Proxy method for starting the trial.
     *
     * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
     */

    public function render()
    {
        if (array_key_exists('email', $this->request_data)) {
            $this->email = $this->request_data['email'];
        }

        return render('components.livewire.billing-portal-purchasev2');
    }
}
