<?php

namespace App\Services;

use App\Models\Party\Party;
use App\Models\Party\PartyPayment;
use App\Models\Party\PartyTransaction;
use App\Models\Purchase\Purchase;
use App\Models\Purchase\PurchaseReturn;
use App\Models\Sale\Sale;
use App\Models\Sale\SaleReturn;
use App\Traits\FormatNumber;

class PartyService
{
    use FormatNumber;

    /**
     * Update Party balance in Party Model
     *
     * */
    public function updatePartyBalance($model): bool
    {
        $itemTransactions = PartyTransaction::selectRaw('sum(to_pay) as to_pay, sum(to_receive) as to_receive')
            ->where('party_id', $model->id)
            ->get()
            ->first();
        $model->to_pay = $itemTransactions->to_pay;
        $model->to_receive = $itemTransactions->to_receive;
        $model->save();

        return true;
    }

    /**
     * Get Party Credit Limit
     * */
    public function getPartyDetails($partyId)
    {
        return Party::find($partyId);
    }

    /**
     * Get Party Credit Limit
     * */
    public function limitThePartyCreditLimit($partyId): bool
    {
        $party = $this->getPartyDetails($partyId);

        if ($party->count() > 0) {

            if ($party->is_set_credit_limit == 0) {
                return true;
            }

            $creditLimit = $party->credit_limit;

            if ($party->party_type == 'customer') {
                // customer
                $dueSaleAmount = $this->saleTransaction($partyId);
                $dueSaleReturnAmount = $this->saleReturnTransaction($partyId);
                $totalDue = $dueSaleAmount - $dueSaleReturnAmount;
                if ($totalDue > $creditLimit) {
                    throw new \Exception(__('party.party_credit_limit_is_exceeding', ['credit_limit' => $this->formatWithPrecision($creditLimit), 'total_due' => $this->formatWithPrecision($totalDue)]));
                }
            } else {
                // supplier
            }
        }

        return false;
    }

    public function saleTransaction($partyId)
    {
        return Sale::selectRaw('coalesce(sum(grand_total) - sum(paid_amount), 0) as due_amount')
            ->where('party_id', $partyId)
            ->get()
            ->first()->due_amount;
    }

    public function saleReturnTransaction($partyId)
    {
        return SaleReturn::selectRaw('coalesce(sum(grand_total) - sum(paid_amount), 0) as due_amount')
            ->where('party_id', $partyId)
            ->get()
            ->first()->due_amount;
    }

    /**
     * Calculate Balance of the Party
     * */
    public function getPartyBalance($partyIds)
    {
        if (empty($partyIds)) {
            return ['balance' => 0, 'status' => ''];
        }

        // Retrieve opening balance from PartyTransaction
        $openingBalance = PartyTransaction::whereIn('party_id', $partyIds)
            ->selectRaw('COALESCE(SUM(to_receive) - SUM(to_pay), 0) as opening_balance')
            ->first()
            ->opening_balance ?? 0;

        // Get Allocation amount based on PartyPayment
        /**
         * For Customers Sale Adjustments
         * */
        $partyPaymentReceiveSum = PartyPayment::whereIn('party_id', $partyIds)
            ->where('payment_direction', 'receive')
            ->selectRaw('
									(SELECT SUM(amount) FROM party_payments
									WHERE party_id IN ('.implode(',', array_map('intval', $partyIds)).') AND payment_direction = "receive")
									-
									COALESCE(
										(SELECT SUM(payment_transactions.amount)
										FROM payment_transactions
										INNER JOIN party_payment_allocations
											ON payment_transactions.id = party_payment_allocations.payment_transaction_id
										WHERE party_payment_allocations.party_payment_id IN
											(SELECT id FROM party_payments
											WHERE party_id IN ('.implode(',', array_map('intval', $partyIds)).') AND payment_direction = "receive")
										), 0)
								AS total_amount')
            ->value('total_amount') ?? 0;

        // Get Allocation amount based on PartyPayment
        /**
         * For suppliers, Purchase adjustments
         * */
        $partyPaymentPaySum = PartyPayment::whereIn('party_id', $partyIds)
            ->where('payment_direction', 'pay')
            ->selectRaw('
									(SELECT SUM(amount) FROM party_payments
									WHERE party_id IN ('.implode(',', array_map('intval', $partyIds)).') AND payment_direction = "pay")
									-
									COALESCE(
										(SELECT SUM(payment_transactions.amount)
										FROM payment_transactions
										INNER JOIN party_payment_allocations
											ON payment_transactions.id = party_payment_allocations.payment_transaction_id
										WHERE party_payment_allocations.party_payment_id IN
											(SELECT id FROM party_payments
											WHERE party_id IN ('.implode(',', array_map('intval', $partyIds)).') AND payment_direction = "pay")
										), 0)
								AS total_amount')
            ->value('total_amount') ?? 0;

        // Sale & Sale Payments
        $saleBalance = Sale::whereIn('party_id', $partyIds)
            ->selectRaw('coalesce(sum(grand_total - paid_amount), 0) as total')
            ->value('total');

        // Sale Return & its Payments
        $saleReturnBalance = SaleReturn::whereIn('party_id', $partyIds)
            ->selectRaw('coalesce(sum(grand_total - paid_amount), 0) as total')
            ->value('total');

        // Purchase & its Payments
        $purchaseBalance = Purchase::whereIn('party_id', $partyIds)
            ->selectRaw('coalesce(sum((grand_total - shipping_charge) -
									CASE
										WHEN paid_amount >= (grand_total - shipping_charge)
										THEN paid_amount - shipping_charge
										ELSE paid_amount
									END), 0) as total')
            ->value('total');

        // Purchase Return & its Payments
        $purchaseReturnBalance = PurchaseReturn::whereIn('party_id', $partyIds)
            ->selectRaw('coalesce(sum(grand_total - paid_amount), 0) as total')
            ->value('total');

        // Calculate as per party
        $balance = $openingBalance - ($partyPaymentReceiveSum - $partyPaymentPaySum);

        // Calculate balance for customers (amount to receive)
        $balance += ($saleBalance) - ($saleReturnBalance);

        // Calculate balance for suppliers (amount to pay)
        $balance -= ($purchaseBalance) - ($purchaseReturnBalance);

        // Determine if the final balance indicates an amount owed or a receivable
        if ($balance > 0) {
            // The company should collect this amount from the party
            return [
                'balance' => $balance,
                'status' => 'you_collect',
            ];
        } elseif ($balance < 0) {
            // The company owes this amount to the party
            return [
                'balance' => abs($balance), // Return positive amount
                'status' => 'you_pay',
            ];
        } else {
            // The balances are equal
            return [
                'balance' => 0,
                'status' => 'no_balance',
            ];
        }
    }
}
