<?php

namespace App\Services\AmazonSelfHealing;

use Carbon\Carbon;
use GuzzleHttp\Client;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Yajny\EventsYj\Events\SaleNew;
use Yajny\EventsYj\Events\SaleUpdated;
use Yajny\Member\Models\AmazonReadLog;
use Yajny\Member\Models\Click;
use Yajny\Member\Models\Sale;
use Yajny\Member\Models\SaleItem;

abstract class AmazonSelfHealingService
{
    public $baseURL = 'https://assoc-datafeeds-eu.amazon.com/datafeed/getReport';
    public $network;
    public $client;
    public $is_sandbox;
    public $considerConfirmedOrders;
    public $processedSales = [];
    public $newSalesIDs = [];

    abstract function getFileName($dateToFetch): string;

    abstract function getAuthUserName(): string;

    abstract function getAuthPassword(): string;

    abstract function getCurrencyCode(): string;

    public function __construct($network, $is_sandbox = false, $considerConfirmedOrders = false)
    {
        $this->client = new Client();
        $this->network = $network;
        $this->is_sandbox = $is_sandbox;
        $this->considerConfirmedOrders = $considerConfirmedOrders;
    }

    public function fetchAndCompare($start_days, $end_days)
    {
        /** Loop From Start Date till End Date and Fetch Files ASC **/
        for ($i = $start_days; $i >= $end_days; $i--) {
            $strToTime = strtotime("today - {$i} days");
            $reportDate = date('Y-m-d', $strToTime);

            try {
                $fileDate = date('Ymd', $strToTime);
                $fileName = $this->getFileName($fileDate);
                if ($this->is_sandbox)
                    dump($reportDate, $fileName);

                if ($this->is_sandbox) {
                    if (Storage::disk('do-spaces')->exists("amazon-test-orders/$fileName"))
                        $salesCompressedResponse = Storage::disk('do-spaces')->get("amazon-test-orders/$fileName");
                    else
                        throw new \Exception('File Not Found On Storage');
                } else {
                    $salesCompressedResponse = $this->client->get($this->baseURL . '?filename=' . $fileName, [
                        'auth' => [
                            $this->getAuthUserName(),
                            $this->getAuthPassword(),
                            'digest'
                        ]
                    ]);
                }

                $responseAttributes = $this->readFile($salesCompressedResponse);
                if (isset($responseAttributes['items'])) {
                    /** Check Log Checksum and Process Files if Checksum is Different **/
                    $log = AmazonReadLog::where('network_id', $this->network->id)->where('file_date', $reportDate)->first();
                    if ($log) {
                        $newItemsChecksum = md5(json_encode($responseAttributes['items']));
                        if (is_null($log->items_checksum) || $log->items_checksum != $newItemsChecksum) {
                            $results = $this->processItems($reportDate, $responseAttributes['items'], $responseAttributes['categories']);

                            $log->last_import = now();
                            $log->items_checksum = $newItemsChecksum;
                            $log->total_items = $results['countItems'];
                            $log->total_orders = $results['countOrders'];
                            $log->is_processed = true;
                            $log->last_inserted_index = $results['countOrders'];
                        }

                        $log->last_check = now();
                        $log->save();
                    }
                }
            } catch (\Exception $exception) {
                if ($this->is_sandbox)
                    dump($exception->getMessage());

                Log::error('Amazon Self Healing Process Error', [
                    'message' => $exception->getMessage(),
                    'fileDate' => $reportDate,
                    'network' => $this->network->toArray(),
                    'exception' => $exception
                ]);
                continue;
            }
        }
    }

    private function readFile($response)
    {
        if ($this->is_sandbox) {
            $xmlDecodedResponse = $response;
        } else {
            $compressedResponse = $response->getBody()->getContents();
            if ($compressedResponse)
                $xmlDecodedResponse = gzdecode($compressedResponse);
            else
                throw new \Exception('Unexpected Empty Response');
        }

        $rawSalesXML = simplexml_load_string($xmlDecodedResponse);
        $rawSales = json_decode(json_encode($rawSalesXML), true);
        if (isset($rawSales['Items']['Item'])) {
            $items = $rawSales['Items']['Item'];
            $categories = Arr::pluck($rawSales['ProductLine_array']['ProductLine'], 'ProductGroupName', 'GLProductGroupID');
            return compact('items', 'categories');
        }

        return ['items' => [], 'categories' => []];
    }

    private function processItems($reportDate, $items, $categories)
    {
        $databaseItems = SaleItem::join('sales', 'sale_items.sale_id', '=', 'sales.id')
            ->where('sales.network_id', $this->network->id)
            ->whereDate('sale_items.report_date', '=', $reportDate)
            ->select('sale_items.*')
            ->get();

        $clicksArr = [];
        $countOrders = 0;
        $countItems = 0;
        foreach ($items as $key => $item) {
            if (strval($key) == '@attributes')
                $saleItem = $item;
            else
                $saleItem = $item['@attributes'];
            if (str_contains($saleItem['SubTag'], '?')) {
                $saleItem['SubTag'] = trim($saleItem['SubTag'], '?');
            }
            if (str_contains($saleItem['SubTag'], ' ')) {
                $saleItem['SubTag'] = trim($saleItem['SubTag'], ' ');
            }

            if (str_contains($saleItem['SubTag'], ',')) {
                $saleItem['SubTag'] = explode(',', $saleItem['SubTag'])[1];
            }
            if (!is_numeric($saleItem['SubTag']))
                continue;

            /** Clean File Data **/
            $unitPrice = str_replace(',', '', $saleItem['Price']);
            $commission = str_replace(',', '', $saleItem['Earnings']);
            $quantity = str_replace(',', '', $saleItem['Qty']);
            $revenue = str_replace(',', '', $saleItem['Revenue']);
            $saleItem['Price'] = doubleval($unitPrice);
            $saleItem['Earnings'] = doubleval($commission);
            $saleItem['Qty'] = doubleval($quantity);
            $saleItem['Revenue'] = doubleval($revenue);
            $saleItem['categoryName'] = $categories[$saleItem['Category']] ?? '';

            /** Get Report Item First Occurrence in DB **/
            $databaseItem = $databaseItems->filter(function ($item) use ($saleItem) {
                return $this->itemCompare($item, $saleItem);
            })->first();

            if ($databaseItem) {
                /** If Report Item is Found in DB Remove it from Collection and Not Process it Again **/
                $databaseItems = $databaseItems->filter(function ($item) use ($databaseItem) {
                    return $item->id != $databaseItem->id;
                });
            } else {
                /** If Report Item is NOT Found in DB Insert New Record and Update the Sale **/
                $sale = $this->updateAndGetSale($saleItem);
                $this->insertSaleItem($sale, $saleItem);
                $this->processedSales[$sale->id] = $sale;
            }

            $countItems++;
            if (!in_array($saleItem['SubTag'], $clicksArr)) {
                $clicksArr[] = $saleItem['SubTag'];
                $countOrders++;
            }
        }

        /** Delete Remaining Items that was NOT Found in Report **/
        $this->deleteSaleItems($databaseItems);

        /** Sync Sales and Raise Events **/
        foreach ($this->processedSales as $saleRecord) {
            $this->syncSale($saleRecord);
            $saleEvent = in_array($saleRecord->id, $this->newSalesIDs) ? new SaleNew($saleRecord) : new SaleUpdated($saleRecord, $saleRecord->getChanges());
            event($saleEvent);
        }

        return compact('countItems', 'countOrders');
    }

    private function itemCompare($db_item, $item)
    {
        return
            $db_item->unit_price == $item['Price'] &&
            $db_item->quantity == $item['Qty'] &&
            $db_item->commission == $item['Earnings'] &&
            $db_item->extras->category_id == $item['Category'];
    }

    private function updateAndGetSale($saleItem)
    {
        $sale = Sale::withoutGlobalScopes()
            ->firstOrNew(['network_id' => $this->network->id, 'network_transaction_id' => $saleItem['SubTag']]);

        $newSale = false;
        if (!$sale->exists) {
            $click = Click::where('id', $saleItem['SubTag'])->first();
            if ($click) {
                $sale->transaction_time = $click->click_time;
            } else {
                $sale->transaction_time = Carbon::parse($saleItem['Date'])->toDateTimeString();
            }

            $sale->click_id = $saleItem['SubTag'];
            $sale->order_id = $saleItem['SubTag'];
            $sale->currency = $this->getCurrencyCode();
            $sale->campaign_id = null;

            $newSale = true;
        } else {
            if (!$this->considerConfirmedOrders && $sale->status == 'confirmed')
                return $sale;
        }
        $sale->save();

        if ($newSale)
            $this->newSalesIDs[] = $sale->id;

        return $sale;
    }

    private function insertSaleItem($sale, $item)
    {
        if (!$this->considerConfirmedOrders && $sale->status == 'confirmed')
            return;

        $saleItem = new SaleItem();
        $saleItem->sale_id = $sale->id;
        $saleItem->category = $item['categoryName'];

        $saleItem->unit_price = $item['Price'];
        $saleItem->commission = $item['Earnings'];
        $saleItem->quantity = $item['Qty'];

        $saleItem->status = $item['Qty'] < 0 ? 'declined' : 'pending';
        $saleItem->report_date = date('Y-m-d', strtotime($item['Date']));;
        $saleItem->extras = [
            'category_id' => $item['Category'],
            'revenue' => $item['Revenue'],
            'date' => $item['Date'],
            'link_type' => $item['LinkType'],
            'rate' => $item['Rate'],
            'seller' => $item['Seller']
        ];
        $saleItem->save();
    }

    private function deleteSaleItems($remainingItems)
    {
        $items = $remainingItems->groupBy('sale_id');
        foreach ($items as $sale_id => $saleItems) {
            $sale = Sale::withoutGlobalScopes()->where('id', $sale_id)->first();
            if (!$this->considerConfirmedOrders && $sale->status == 'confirmed')
                continue;

            $saleItemsIDs = $saleItems->pluck('id')->toArray();
            SaleItem::whereIn('id', $saleItemsIDs)->delete();

            $this->processedSales[$sale->id] = $sale;
        }
    }

    private function syncSale($sale)
    {
        $orderAmount = 0;
        $commission = 0;
        foreach ($sale->items as $saleItem) {
            $orderAmount += $saleItem->quantity * $saleItem->unit_price;
            $commission += $saleItem->commission;
        }

        $sale->local_order_amount = $orderAmount;
        $sale->local_comission = $commission;
        $sale->network_status = $sale->local_comission > 0 ? 'pending' : 'rejected';
        $sale->setStatus($this->network->sale_statuses);
        $sale->save();
    }
}
