<?php

namespace App\Classes\Modules\Accounting\Processors;

use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\PaymentMethodType;
use App\Classes\ValueObjects\Constants\StatementTransactionOwnerType;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\Booking;
use App\Models\Company;
use App\Models\Group;
use App\Models\StatementTransaction;
use App\Models\Transaction;
use App\Models\Wallet;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;

class CreateBankStatementTransactionOwnersProcessor
{

    /**
     * @return void
     */
   public function execute($transactions) {

//       $transactions = StatementTransaction::whereDoesntHave('owners')->where('amount', '<', 0)->get();

        foreach ($transactions as $transaction) {
            $mapped = false;
            $keywords = array_filter(explode(" ", $transaction->transaction_description . " " . $transaction->transaction_description_2));

            if($transaction->amount > 0){

                // Exchange Sales
                $creditTransactions = $this->getTransactions($transaction->posting_date, $transaction->amount, TransactionType::PAYMENT, Booking::class, PaymentMethodType::WALLET, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED], $keywords);
                foreach ($creditTransactions as $creditTransaction) {
                    $isArray = is_array($creditTransaction);
                    $data = $transaction->owners()->firstOrCreate([
                            'type' => StatementTransactionOwnerType::SALES,
                            'system' => 'EXCHANGE',
                            'owner_type' => Transaction::class,
                            'owner_id'=> $isArray ? $creditTransaction['owner_id'] : $creditTransaction->id,
                            'owner_reference'=> $isArray ? $creditTransaction['owner_reference'] : $creditTransaction->owner->marking,
                    ]);
                }

                // Shipping Portal Sales
                $creditTransactions = $this->getTransactionsFromShippingPortal($transaction->amount, $this->getDateRange($transaction->posting_date, 1), [2], PaymentMethodType::WALLET);
                foreach ($creditTransactions as $creditTransaction) {
                    if($creditTransaction['owner_type'] === Wallet::class) continue;
                    $data = $transaction->owners()->firstOrCreate([
                        'type' => StatementTransactionOwnerType::SALES,
                        'system' => 'SHIPPING_PORTAL',
                        'owner_type' => $creditTransaction['owner_type'],
                        'owner_id'=> $creditTransaction['owner_id'],
                        'owner_reference'=> $creditTransaction['owner_reference'],
                    ]);
                }

                // Exchange Wallet Top Up
                $creditTransactions = $this->getTransactions($transaction->posting_date, $transaction->amount, TransactionType::TOP_UP, Wallet::class, null, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED], $keywords);
                foreach ($creditTransactions as $creditTransaction) {
                    $isArray = is_array($creditTransaction);
                    $data = $transaction->owners()->firstOrCreate([
                            'type' => StatementTransactionOwnerType::WALLET_TOP_UP,
                            'system' => 'EXCHANGE',
                            'owner_type' => Transaction::class,
                            'owner_id'=> $isArray ? $creditTransaction['owner_id'] : $creditTransaction->id,
                            'owner_reference'=> $isArray ? $creditTransaction['owner_reference'] : $creditTransaction->owner->owner->reference,
                    ]);
                }

                $creditTransactions = $this->getTransactionsFromShippingPortal($transaction->amount, $this->getDateRange($transaction->posting_date, 1), [5,15], null);
                foreach ($creditTransactions as $creditTransaction) {
                    $data = $transaction->owners()->firstOrCreate([
                        'type' => StatementTransactionOwnerType::WALLET_TOP_UP,
                        'system' => 'SHIPPING_PORTAL',
                        'owner_type' => $creditTransaction['owner_type'],
                        'owner_id'=> $creditTransaction['owner_id'],
                        'owner_reference'=> $creditTransaction['owner_reference'],
                    ]);
                }

                // fpx charge refund
                if($transaction->transaction_description === 'DUITNOW S/CHRG REFUND'){
                    $data = $transaction->owners()->firstOrCreate([
                        'type' => StatementTransactionOwnerType::FPX_CHARGE_REFUND
                    ]);
                }

                // Customer Refund

                // INTERNAL_BANK_TRANSFER_IN
                if(str_contains($transaction->transaction_description_2, 'CIEF WORLDWIDE')){
                    $data = $transaction->owners()->firstOrCreate([
                        'type' => StatementTransactionOwnerType::INTERNAL_BANK_TRANSFER_IN
                    ]);
                }


            }

            if($transaction->amount < 0){

                // Supplier Purchase Order Payments
                foreach (['YSN', 'HCK', 'ATVANTIC', 'HIGH HILL'] as $reference){
                    if(str_contains($transaction->transaction_description.' '.$transaction->transaction_description_2.' '.$transaction->transaction_description_3.' '.$transaction->transaction_description_4.' '.$transaction->transaction_description_5 , $reference)) {
                        $paymentDateStart = $transaction->posting_date->startOfDay()->subDays(1);
                        $paymentDateEnd = $transaction->posting_date->endOfDay();

                        if($paymentDateStart->dayOfWeek === Carbon::SUNDAY){
                            $paymentDateStart->subDays(2);
                        }
                        $issuer = Company::where('name', 'like', '%'.$reference.'%')->get()->pluck('id');

                        $debitTransactions = Group::whereIn('issuer', $issuer)->where('amount', '>=', (($transaction->amount * -1) - 0.01))
                            ->where('amount', '<=', (($transaction->amount * -1) + 0.01))->whereDate('created_at', '>=', $paymentDateStart)->whereDate('created_at', '<=', $paymentDateEnd)->get();

                        foreach ($debitTransactions as $debitTransaction) {
                            $data = $transaction->owners()->firstOrCreate([
                                'type' => StatementTransactionOwnerType::SUPPLIER_PAYMENT,
                                'system' => 'EXCHANGE',
                                'owner_type' => Group::class,
                                'owner_id'=> $debitTransaction->id,
                                'owner_reference'=> $debitTransaction->reference
                            ]);
                        }

                    }
                }

                // Exchange Wallet Withdrawal
                $debitTransactions = $this->getTransactions($transaction->posting_date, $transaction->amount, TransactionType::DEBIT_NOTE, Wallet::class, null, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED], $keywords);
                foreach ($debitTransactions as $debitTransaction) {
                    $isArray = is_array($creditTransaction);
                    $data = $transaction->owners()->firstOrCreate([
                        'type' => StatementTransactionOwnerType::WALLET_WITHDRAWAL,
                        'system' => 'EXCHANGE',
                        'owner_type' => Transaction::class,
                        'owner_id'=> $isArray ? $debitTransaction['owner_id'] : $debitTransaction->id,
                        'owner_reference'=> $isArray ? $debitTransaction['owner_reference'] : $debitTransaction->owner->owner->marking,
                    ]);
                }

                // SALARY

                // STATUTORY
                if(str_contains($transaction->transaction_description_2, 'PEMBANGUNAN SUMBER') || str_contains($transaction->transaction_description_2, 'HASIL') || str_contains($transaction->transaction_description_2, 'PERTUBUHAN KESELAMAT') || str_contains($transaction->transaction_description_2, 'KUMPULAN WANG SIMPAN')){
                    $data = $transaction->owners()->firstOrCreate([
                        'type' => StatementTransactionOwnerType::STATUTORY
                    ]);
                }

                // FPX_CHARGE
                if($transaction->transaction_description === 'DR DUITNOW S/CHRG' || str_contains($transaction->transaction_description, 'Manual FPX') || str_contains($transaction->transaction_description, 'CMS - DR FPX CHG')){
                    $data = $transaction->owners()->firstOrCreate([
                        'type' => StatementTransactionOwnerType::FPX_CHARGE
                    ]);
                }

                // BANK_CHARGE
                if($transaction->transaction_description === 'CMS - DR CORP CHG' || $transaction->transaction_description === 'MONTHLY PROFIT DEBIT'){
                    $data = $transaction->owners()->firstOrCreate([
                        'type' => StatementTransactionOwnerType::BANK_CHARGE
                    ]);
                }

                // CREDIT_CARD_PAYMENT
                if(str_contains($transaction->transaction_description_2, 'VISA CARD')){
                    $data = $transaction->owners()->firstOrCreate([
                        'type' => StatementTransactionOwnerType::CREDIT_CARD_PAYMENT
                    ]);
                }

                // INTERNAL_BANK_TRANSFER_OUT
                if(str_contains($transaction->transaction_description_2, 'CIEF WORLDWIDE') || str_contains($transaction->transaction_description_2, 'CIEF WORLWIDE') || str_contains($transaction->transaction_description_2, 'IZYIM GLOBAL')){
                    $data = $transaction->owners()->firstOrCreate([
                        'type' => StatementTransactionOwnerType::INTERNAL_BANK_TRANSFER_OUT
                    ]);
                }

                // non-operational charges
                if(str_contains($transaction->transaction_description_2, 'HIRE PURCHASE') || str_contains($transaction->transaction_description_2, 'TENAGA NASIONAL') || str_contains($transaction->transaction_description, 'CABLE CHARGE') || str_contains($transaction->transaction_description_2, 'CTOS DATA SYSTEMS') || str_contains($transaction->transaction_description_2, 'MAXIS')){
                    $data = $transaction->owners()->firstOrCreate([
                        'type' => StatementTransactionOwnerType::NON_OPERATIONAL
                    ]);
                }
            }
            if (isset($data) && $data->wasRecentlyCreated) $mapped = true;
            $this->updateMappedRate($transaction, $mapped);
        }
    }

    private function getTransactions($date, $amount, $type, $ownerType, $paymentMethod, $statuses, $keywords, $model = Transaction::class) {
        $dateRange = $this->getDateRange($date, 4);    
        if (App::environment(['production'])) {
            $query = $model::whereIn('status', $statuses)
                ->where(function ($query) use ($ownerType, $paymentMethod, $type) {
                    if ($ownerType) {
                        $query->where('owner_type', $ownerType);
                    }

                        if ($paymentMethod) {
                            $query->where('payment_method', '!=', $paymentMethod);
                        }

                    if ($type) {
                        $query->where('type', $type);
                    }
                })
                ->whereDate('created_at', '>=', $dateRange['start_date'])
                ->whereDate('created_at', '<=', $dateRange['end_date'])
                ->where('amount', '>', ($amount - 0.05))
                ->where('amount', '<', ($amount + 0.05));

            if ($query->count() > 1) {
                $transactions = clone $query;

                $transactions->whereDate('created_at', $date->format('Y-m-d'));

                if ($transactions->count() === 1) {
                    return $transactions->get();
                }

                $transactions = clone $query;

                if ($ownerType === Booking::class) {
                    $transactions = $transactions->whereHasMorph('owner', [Booking::class], function ($q1) use ($keywords) {
                        return $q1->whereHas('company', function ($company) use ($keywords) {
                            return $company->where(function ($q2) use ($keywords) {
                                foreach($keywords as $keyword) {
                                    $q2->orWhere('name', 'LIKE', '%'.$keyword.'%');
                                }
                                $q2->orWhereHas('contacts', function ($contact) use($keywords) {
                                    return $contact->where(function ($q3) use($keywords) {
                                        foreach($keywords as $keyword) {
                                            $q3->orWhere('reference', 'LIKE', '%'.$keyword.'%');
                                        }
                                    });
                                });
                            });
                        });
                    });
                }
             
                if ($ownerType === Wallet::class) {
                    $transactions = $transactions->whereHasMorph('owner', [Wallet::class], function ($q1) use ($keywords) {
                        return $q1->whereHasMorph('owner', [Company::class], function ($company) use($keywords) {
                            return $company->where(function ($q2) use ($keywords) {
                                foreach($keywords as $keyword) {
                                    $q2->orWhere('name', 'LIKE', '%'.$keyword.'%');
                                }
                                $q2->orWhereHas('contacts', function ($contact) use($keywords) {
                                    return $contact->where(function ($q3) use($keywords) {
                                        foreach($keywords as $keyword) {
                                            $q3->orWhere('reference', 'LIKE', '%'.$keyword.'%');
                                        }
                                    });
                                });
                            });
                        });
                    });                    
                }

                if ($transactions->count() === 1) {
                    return $transactions->get();
                }
            } 

            return $query->get();
        } else {
            $result = $this->getTransactionsFromExchange($amount, $dateRange, $type, $ownerType, $paymentMethod);

            return $result;
        }
    }

    private function getTransactionsFromExchange($amount, $dateRange, $type, $ownerType, $paymentMethod){
        $url = 'https://exchange.cief-malaysia.com/public/api/v1/transactions/mappable/query';
        return $this->getFromExchange($url, $amount, $dateRange, $type, $ownerType, $paymentMethod);
    }

    private function getFromExchange($url, $amount, $dateRange, $type, $ownerType, $paymentMethod){
        $paymentMethodFilter = "";
        if ($paymentMethod) {
            $paymentMethodFilter = ',"payment_method_not_in":['.$paymentMethod.']';
        }
        $ownerType = addslashes($ownerType);
        try{
            $client = new \GuzzleHttp\Client(['verify' => false]);
            $response = $client->request('GET', $url.'?api-key=510acd13d8d24375cf038ad626c282565451461a9c2399357e0b65365300787e&filters={"order_by":{"column":"id","DESC":true},"status_in":[2,3],"owner_type":"'. $ownerType . '"' . $paymentMethodFilter.',"type":'.$type.',"created_after_or_equal":"'.$dateRange['start_date'].'","created_before_or_equal":"'.$dateRange['end_date'].'","amount_exceed":'.($amount - 0.01).',"amount_short":'.($amount + 0.01).'}');
            $body = $response->getBody();
            $data = json_decode($body, true);
            $payload = $data['payload'];
            $transactions2 = $payload['data'];
            return $transactions2;
        }catch(\Exception $exception){
            Log::error($exception);
            return [];
        }
    }

    private function getTransactionsFromShippingPortal($amount, $dateRange, $type, $paymentMethod){
       $url = 'https://izyim.cief-malaysia.com/public/api/v1/transactions/mappable/query';
        return $this->getFromShippingPortal($amount, $dateRange, $url, $type, $paymentMethod);
    }

    private function getGroupsFromShippingPortal($amount, $dateRange, $type, $paymentMethod){
        $url = 'https://izyim.cief-malaysia.com/public/api/v1/groups/query';
        return $this->getFromShippingPortal($amount, $dateRange, $url, $type, $paymentMethod);
    }

    private function getFromShippingPortal($amount, $dateRange, $url, $type, $paymentMethod){
        $paymentMethodFilter = "";
        if ($paymentMethod) {
            $paymentMethodFilter = ',"payment_method_not_in":['.$paymentMethod.']';
        }
        try{

            $client = new \GuzzleHttp\Client(['verify' => false]);
            $response = $client->request('GET', $url.'?api-key=510acd13d8d24375cf038ad626c282565451461a9c2399357e0b65365300787e&filters={"order_by":{"column":"id","DESC":true},"status_in":[2]'.$paymentMethodFilter.',"created_after":"'.$dateRange['start_date'].'","created_before":"'.$dateRange['end_date'].'","amount_exceed":'.($amount - 0.01).',"amount_short":'.($amount + 0.01).',"type_in":'.json_encode($type).'}');
            $body = $response->getBody();
            $data = json_decode($body, true);
            $payload = $data['payload'];
            $transactions2 = $payload['data'];
            return $transactions2;
        }catch(\Exception $exception){
            Log::error($exception);
            return [];
        }
    }

    private function getDateRange(string $dateStr, int $range) {
        // Create a DateTime object from the input string
        $date = strtotime($dateStr);

        // Get the first day of the month
        $today = date('Y-m-d', strtotime('-' . $range . ' day', $date));

        // Get the first day of the next month
        $nextDay = date('Y-m-d', strtotime('+' . $range . ' day', $date));

        return [
            'start_date' => $today,
            'end_date' => $nextDay,
        ];
    }

    private function updateMappedRate($transaction, $mapped) {
        $statement = $transaction->statement;
        $statement->total_rows = StatementTransaction::where('account_statement_id',$transaction->account_statement_id)->count();
        $statement->mapped_rows = $mapped ? $statement->mapped_rows+1 : $statement->mapped_rows;
        $statement->save();
    }
}
