<?php

namespace App\Http\Controllers\Network\Classes;

use Carbon\Carbon;
use Exception;
use GuzzleHttp\Client;
use Illuminate\Support\Arr;
use Yajny\Member\Models\AmazonOrderLog;
use Yajny\Member\Models\AmazonReadLog;
use Yajny\Member\Models\Click;

abstract class BaseAmazon_BK implements SalesGetterApi
{
    public $baseURL = 'https://assoc-datafeeds-eu.amazon.com/datafeed/getReport';
    public $client;
    public $sales;
    public $orders;
    private $networkID;

    public function __construct($networkID)
    {
        $this->client = new Client();
        $this->sales = [];
        $this->orders = [];
        $this->networkID = $networkID;
    }

    abstract function getFileName($dateToFetch): string;

    abstract function getOrdersFileName($dateToFetch): string;

    abstract function getAuthUserName(): string;

    abstract function getAuthPassword(): string;

    abstract function getCurrencyCode(): string;

    /**
     * Getting sales from network and prepare it to be ready for saving and log amazon files reading.
     *
     * @param DateTime $start_days filter sales based on start days
     * @param DateTime $end_days filter sales based on end days
     *
     * @return array sales orders
     * @throws Exception If issue happened on saving sales
     *
     */
    //TODO splitting this big function
    public function getSales($start_days, $end_days)
    {
        $this->getOrders($start_days, $end_days);
        try {
            $sales = [];

            $data = $this->getLastFailedEarning();
            $dateToFetch = $data['dateToFetch'];
            $isFailedDate = $data['isFailedDate'];
            $lastFailed = $data['lastFailed'];
            //check that we are not trying to get today's file since Amazon uploads the reports delayed 1 day
            if ($dateToFetch == date('Y-m-d'))
                return [];
            $nextFileDate = date('Ymd', strtotime($dateToFetch));
            $fileName = $this->getFileName($nextFileDate);
            $salesCompressedResponse = $this->client->get($this->baseURL . '?filename=' . $fileName, [
                'auth' => [
                    $this->getAuthUserName(),
                    $this->getAuthPassword(),
                    'digest'
                ]
            ]);

            $checksum = null;
            $itemsCount = null;
            $compressedResponse = $salesCompressedResponse->getBody()->getContents();
            if ($compressedResponse) {
                $data = $this->getSalesFromEarningFile($compressedResponse, $dateToFetch);
                $sales = $data['sales'];
                $itemsCount = $data['itemsCount'];
                $checksum = $data['checksum'];
            } else {
                throw new Exception('Unexpected Empty Response');
            }

            if (!$isFailedDate) {
                AmazonReadLog::create([
                    'file_date' => $dateToFetch,
                    'file_name' => $fileName,
                    'network_id' => $this->networkID,
                    'is_processed' => count($sales) == 0,
                    'total_orders' => count($sales),
                    'total_items' => !is_null($itemsCount) ? $itemsCount : 0,
                    'last_inserted_index' => 0,
                    'items_checksum' => $checksum,
                    'last_check' => now(),
                    'last_import' => now()
                ]);
            } else {
                $lastFailed->total_orders = count($sales);
                $lastFailed->total_items = !is_null($itemsCount) ? $itemsCount : 0;
                $lastFailed->items_checksum = $checksum;
                $lastFailed->last_check = now();
                $lastFailed->last_import = now();
                $lastFailed->save();

                return $this->sales = array_slice(array_values($sales), $lastFailed->last_inserted_index);
            }

            if (!$sales) return [];
            return $this->sales = $sales;

        } catch (Exception $e) {
            return [];
        }
    }

    public function getOrders($start_days, $end_days)
    {
        try {
            $orders = [];

            $data = $this->getLastFailedOrders();
            $dateToFetch = $data['dateToFetch'];
            $isFailedDate = $data['isFailedDate'];
            $lastFailed = $data['lastFailed'];
            //check that we are not trying to get today's file since Amazon uploads the reports delayed 1 day
            if ($dateToFetch == date('Y-m-d'))
                return [];
            $nextFileDate = date('Ymd', strtotime($dateToFetch));
            $fileName = $this->getOrdersFileName($nextFileDate);
            $salesCompressedResponse = $this->client->get($this->baseURL . '?filename=' . $fileName, [
                'auth' => [
                    $this->getAuthUserName(),
                    $this->getAuthPassword(),
                    'digest'
                ]
            ]);

            $checksum = null;
            $itemsCount = null;
            $compressedResponse = $salesCompressedResponse->getBody()->getContents();
            if ($compressedResponse) {
                $data = $this->getSalesFromOrdersFile($compressedResponse, $dateToFetch);
                $orders = $data['sales'];
                $itemsCount = $data['itemsCount'];
                $checksum = $data['checksum'];
            } else {
                throw new Exception('Unexpected Empty Response');
            }

            if (!$isFailedDate) {
                AmazonReadLog::create([
                    'file_date' => $dateToFetch,
                    'file_name' => $fileName,
                    'network_id' => $this->networkID,
                    'is_processed' => count($orders) == 0,
                    'total_orders' => count($orders),
                    'total_items' => !is_null($itemsCount) ? $itemsCount : 0,
                    'last_inserted_index' => 0,
                    'items_checksum' => $checksum,
                    'last_check' => now(),
                    'last_import' => now()
                ]);
            } else {
                $lastFailed->total_orders = count($orders);
                $lastFailed->total_items = !is_null($itemsCount) ? $itemsCount : 0;
                $lastFailed->items_checksum = $checksum;
                $lastFailed->last_check = now();
                $lastFailed->last_import = now();
                $lastFailed->save();

                return $this->orders = array_slice(array_values($orders), $lastFailed->last_inserted_index);
            }
            if (!$orders) return [];
            return $this->orders = $orders;

        } catch (Exception $e) {
            return [];
        }
    }

    public function getLastFailedEarning(): array
    {
        $dateToFetch = '2021-09-01';
        $lastFailed = AmazonReadLog::where([['is_processed', false], ['network_id', $this->networkID], ['file_name', 'LIKE', '%earnings%']])->first();

        $isFailedDate = false;
        if ($lastFailed) {
            $dateToFetch = $lastFailed->file_date;
            $isFailedDate = true;
        } else {
            $lastFetchedDate = AmazonReadLog::where([['is_processed', true], ['network_id', $this->networkID], ['file_name', 'LIKE', '%earnings%']])->max('file_date');
            $dateToFetch = date('Y-m-d', strtotime(($lastFetchedDate ?? $dateToFetch) . ' + 1 day'));
        }
        return compact('dateToFetch', 'isFailedDate', 'lastFailed');
    }

    public function getLastFailedOrders(): array
    {
        $dateToFetch = '2021-09-01';
        $lastFailed = AmazonReadLog::where([['is_processed', false], ['network_id', $this->networkID], ['file_name', 'LIKE', '%orders%']])->first();

        $isFailedDate = false;
        if ($lastFailed) {
            $dateToFetch = $lastFailed->file_date;
            $isFailedDate = true;
        } else {
            $lastFetchedDate = AmazonReadLog::where([['is_processed', true], ['network_id', $this->networkID], ['file_name', 'LIKE', '%orders%']])->max('file_date');
            $dateToFetch = date('Y-m-d', strtotime(($lastFetchedDate ?? $dateToFetch) . ' + 1 day'));
        }
        return compact('dateToFetch', 'isFailedDate', 'lastFailed');
    }

    public function getSalesFromEarningFile($compressedResponse, $dateToFetch): array
    {
        $itemsCount = null;
        $checksum = null;
        $sales = [];
        $xmlDecodedResponse = gzdecode($compressedResponse);
        $rawSalesXML = simplexml_load_string($xmlDecodedResponse);
        $rawSales = json_decode(json_encode($rawSalesXML), true);
        $categories = Arr::pluck($rawSales['ProductLine_array']['ProductLine'], 'ProductGroupName', 'GLProductGroupID');
        if (isset($rawSales['Items']['Item'])) {
            $itemsCount = 0;
            $checksum = md5(json_encode($rawSales['Items']['Item']));
            foreach ($rawSales['Items']['Item'] as $key => $item) {
                if (strval($key) == '@attributes')
                    $saleItem = $item;
                else
                    $saleItem = $item['@attributes'];

                if (!is_numeric($saleItem['SubTag']))
                    continue;

                $saleItem['categoryName'] = $categories[$saleItem['Category']] ?? '';
                if ($saleItem['Qty'] < 0)
                    $saleItem['Type'] = 'Return';

                if (isset($sales[$saleItem['SubTag']])) {
                    $sales[$saleItem['SubTag']]['items'][] = $saleItem;
                } else {
                    $sales[$saleItem['SubTag']] = $saleItem;
                    $sales[$saleItem['SubTag']]['currencyCode'] = $this->getCurrencyCode();
                    $sales[$saleItem['SubTag']]['file_date'] = $dateToFetch;
                    $sales[$saleItem['SubTag']]['items'] = [$saleItem];
                }
                $itemsCount++;
            }
        }
        return compact('sales', 'itemsCount', 'checksum');
    }

    public function getSalesFromOrdersFile($compressedResponse, $dateToFetch): array
    {
        $itemsCount = null;
        $checksum = null;
        $sales = [];
        $xmlDecodedResponse = gzdecode($compressedResponse);
        $rawSalesXML = simplexml_load_string($xmlDecodedResponse);
        $rawSales = json_decode(json_encode($rawSalesXML), true);
        $categories = Arr::pluck($rawSales['ProductLine_array']['ProductLine'], 'ProductGroupName', 'GLProductGroupID');
        if (isset($rawSales['Items']['Item'])) {
            $itemsCount = 0;
            $checksum = md5(json_encode($rawSales['Items']['Item']));
            foreach ($rawSales['Items']['Item'] as $key => $item) {
                if (strval($key) == '@attributes')
                    $saleItem = $item;
                else
                    $saleItem = $item['@attributes'];

                if (!is_numeric($saleItem['SubTag']))
                    continue;

                $saleItem['categoryName'] = $categories[$saleItem['Category']] ?? '';
                if ($saleItem['Qty'] < 0)
                    $saleItem['Type'] = 'Return';
                $amazonOrderLog = new AmazonOrderLog();
                $amazonOrderLog->file_date = $dateToFetch;
                $amazonOrderLog->click_id = $saleItem['SubTag'];
                $amazonOrderLog->price = $saleItem['Price'];
                $amazonOrderLog->qty = $saleItem['Qty'];
                $amazonOrderLog->category = $saleItem['categoryName'];
                $amazonOrderLog->network_id = $this->networkID;
                $amazonOrderLog->currency_code = $this->getCurrencyCode();
                $amazonOrderLog->save();
                if (isset($sales[$saleItem['SubTag']])) {
                    $sales[$saleItem['SubTag']]['items'][] = $saleItem;
                } else {
                    $sales[$saleItem['SubTag']] = $saleItem;
                    $sales[$saleItem['SubTag']]['currencyCode'] = $this->getCurrencyCode();
                    $sales[$saleItem['SubTag']]['file_date'] = $dateToFetch;
                    $sales[$saleItem['SubTag']]['items'] = [$saleItem];
                }
                $itemsCount++;
            }
        }
        return compact('sales', 'itemsCount', 'checksum');
    }

    public function saveOrdersToAmazonOrders($saleOrder)
    {
        $sale = AmazonOrderLog::withoutGlobalScopes()->firstOrNew(['network_id' => $this->networkID, 'click_id' => $saleOrder['SubTag']]);
        $orderAmount = 0;
        if (!$sale->exists) {
            $click = Click::where('id', $saleOrder['SubTag'])->first();
            if ($click) {
                $sale->transaction_time = $click->click_time;
            } else {
                $sale->transaction_time = Carbon::parse($saleOrder['Date'])->toDateTimeString();
            }

            $sale->click_id = $saleOrder['SubTag'];
            $sale->currency_code = $saleOrder['currencyCode'];
        } else {
            $orderAmount = $sale->price;
        }
        $newOrderAmount = 0;

        foreach ($saleOrder['items'] as $item) {
            $revenue = str_replace(',', '', $item['Price']);
            $qty = str_replace(',', '', $item['Qty']);
            /** Accumulate Order Amount and Commission regardless negative values **/
            $newOrderAmount += doubleval($revenue);
        }
        $sale->price = $orderAmount + $newOrderAmount;
        $sale->file_date = $saleOrder['file_date'];
        $sale->save();
    }
}
