<?php

namespace App\Services\Reports\CycleCount;

use App\Models\Asset;
use App\Models\AssetStatus;
use App\Models\AssetType;
use App\Models\CycleCountScannedData;
use Facades\App\Repositories\AssetRepository;
use Illuminate\Support\Facades\DB;

/**
 * Service Class for CycleCountReportService Report
 *
 * Assets Defference between the physical location and Database location
 */
class CycleCountReportService
{
    public $excludedAssetTypes;

    public $excludedAssetStatuses;

    public function __construct()
    {
        $this->excludedAssetTypes = config('cycle-count.excluded-asset-types');
        $this->excludedAssetStatuses = config('cycle-count.excluded_asset_statuses');
    }

    /**
     * Get array of selected asset types
     * @return array
     */
    public function getSelectedAssetTypes()
    {
        $assetTypeIds = request('asset_type_id');
        if (!is_array($assetTypeIds)) {
            return [$assetTypeIds];
        }

        return $assetTypeIds;
    }

    /**
     * Exclude the assetags/serial nos from given tags or serialnos array based on conditions.
     * @param array $serialNos
     * @param mixed $assetTypeId
     * 
     * @return [type]
     */
    public function excludeAssetsFromAssetTagsArray(array $serialNos, $selectedAssetTypeIds)
    {

        $reqAssets = Asset::select('id', 'asset_type_id', 'serial_no', 'asset_tag')->with('assetType')->whereIn('serial_no', $serialNos)->orWhereIn('asset_tag', $serialNos)->get();
        foreach ($reqAssets as $key => $reqAsset) {
            if (in_array($reqAsset->assetType->slug, $this->excludedAssetTypes)) {
                //if ($reqAsset->assetType->slug == 'mobile_phone_number') {
                if (($assetKey = array_search($reqAsset->serial_no, $serialNos)) !== false) {
                    unset($serialNos[$assetKey]);
                } elseif (($assetKey = array_search($reqAsset->serial_no, $serialNos)) !== false) {
                    unset($serialNos[$assetKey]);
                }
            }
            // Checks whether $selectedAssetTypeIds is not empty , and whether the asset type ID of $reqAsset is not in the array $selectedAssetTypeIds, and whether the string "All" is not in the array $selectedAssetTypeIds.
            if (($selectedAssetTypeIds != '' || $selectedAssetTypeIds != []) && (!in_array($reqAsset->asset_type_id, $selectedAssetTypeIds) && !in_array("All", $selectedAssetTypeIds))) {
                // below conditions checks whether the asset tag or serial # of $reqAsset exists in the array $serialNos.
                //If the asset tag or serial # exists in the array $serialNos, remove it.
                if (($assetKey = array_search($reqAsset->serial_no, $serialNos)) !== false) {
                    unset($serialNos[$assetKey]);
                } elseif (($assetKey = array_search($reqAsset->serial_no, $serialNos)) !== false) {
                    unset($serialNos[$assetKey]);
                }
            }
        }
        return $serialNos;
    }

    /**
     * Get names of all scanned users in a comma seperated string.
     * 
     * @param mixed $locationId
     * @param mixed $date
     * 
     * @return [type]
     */
    public function getUserNamesForVarianceReport($locationId, $date)
    {
        $userName = '';
        $users = CycleCountScannedData::select('users.id', 'users.first_name', 'users.last_name')
            ->rightJoin('users', 'users.id', '=', 'cycle_count_scanned_datas.user_id')
            ->where('cycle_count_scanned_datas.location_id', $locationId)
            ->whereDate('cycle_count_scanned_datas.created_at', '=', $date)
            ->groupBy('users.id')
            ->get();

        foreach ($users as $user) {
            $userName .= $user->first_name . ' ' . $user->last_name . ", ";
        }

        //remove the trailing ', '
        $userName = rtrim($userName, ", ");

        return $userName;
    }

    /**
     * Get all scanned asset details
     *
     * @param array  $assetTags    Scanned asset tags
     * @param object $location     App\Models\Location object
     *
     * @return array()
     */
    public function getScannedAssetData($assetTags, $location)
    {
        $excudedAssetTypesIdArray = AssetType::getAssetTypesExcludedInVarianceReport()->pluck('id')->toArray();
        $excludedAssetStatusesIdArray = AssetStatus::getAssetStatusesExcludedInVarianceReport()->pluck('id')->toArray();

        $assets = Asset::with(['assetType:id,name', 'makeAndModel.manufacturer', 'technicalSpec:id,details', 'assetStatus:id,name', 'location:id,room_name'])
            ->excludeAssetsByAssetType($excudedAssetTypesIdArray)
            ->excludeAssetsByStatus($excludedAssetStatusesIdArray)
            ->getAssetByAssetTagsOrSerialNumbers($assetTags)
            ->filterOutStolenOrLostAssetsDatedMoreThanSpecifiedDays()
            ->get();

        $datas = $assets->mapWithKeys(function ($item, $key) use ($location) {
            $match = $item['location_id'] == $location->id ? 'Y' : 'N';

            $discrepancy = $this->getDiscrepancy($item, $match);
            $error = 'Process Error';
            if ($match == 'Y') {
                $error = '';
            }

            return [
                $key => $this->getKeyData($item, $match, $discrepancy, $error)
            ];
        });

        return $datas;
    }

    /**
     * Get not scanned asset details from the given location
     *
     * @param array  $assetTags    Scanned asset tags
     * @param object $location     App\Models\Location object
     *
     * @return array()
     */
    public function getNotScannedAssetData($assetTags, $location, $assetTypeId = [])
    {
        $excudedAssetTypesIdArray = AssetType::getAssetTypesExcludedInVarianceReport()->pluck('id')->toArray();
        $excludedAssetStatusesIdArray = AssetStatus::getAssetStatusesExcludedInVarianceReport()->pluck('id')->toArray();

        if (($assetTypeKey = array_search('', $assetTypeId)) !== false) {
            unset($assetTypeId[$assetTypeKey]);
        }

        // $mobilePhoneNumber = AssetType::where('slug', 'mobile_phone_number')->first();
        $assetsNotScanned = Asset::with(['makeAndModel.manufacturer', 'technicalSpec:id,details', 'assetType:id,name', 'assetStatus:id,name', 'location:id,room_name'])
            ->excludeAssetsByAssetType($excudedAssetTypesIdArray)
            ->excludeAssetsByStatus($excludedAssetStatusesIdArray)
            ->excludeAssetsByAssetTagsOrSerialNumbers($assetTags)
            ->getAssetsByLocationId($location->id)
            ->filterOutStolenOrLostAssetsDatedMoreThanSpecifiedDays();

        if ($assetTypeId != []) {
            $assetsNotScanned = $assetsNotScanned->whereIn('asset_type_id', $assetTypeId);
        }


        $datasNotScanned = collect([]);
        $datasNotScanned = $assetsNotScanned->get()->mapWithKeys(function ($item, $key) use ($location) {
            $discrepancy = 'Not scanned';
            $error = 'Unknown Shrinkage';
            $match = 'N';
            return [
                $key => $this->getKeyData($item, $match, $discrepancy, $error)
            ];
        });

        return $datasNotScanned;
    }


    /**
     * Generating Asset Details to Show in Vairance Report generating page
     *
     * @param object  $item         Asset Object
     * @param string  $match        Asset Matched/Not with location (Y or N)
     * @param string  $discrepancy  Descrepency string
     *
     * @return array()
     */
    public function getKeyData($item, $match, $discrepancy, $error = '')
    {
        return [
            'asset_tag' => $item->asset_tag,
            'serial_no' => $item->serial_no,
            'asset_type' => optional($item->assetType)->name,
            'hardware_standard' => optional($item->makeAndModel)->makeModelName,
            'technical_spec' => optional($item->technicalSpec)->details,
            'status' => optional($item->assetStatus)->name,
            'location' => optional($item->location)->room_name,
            'location_match' => $match,
            'discrepancy' => $discrepancy,
            'error' => $error,
        ];
    }

    /**
     * Get scanned but not in the databse asset details
     *
     * @param array  $assetTags    Scanned asset tags
     * @param object $location     App\Models\Location object
     *
     * @return array()
     */
    public function getNotInDatabaseAssetData($assetTags, $location)
    {
        $existingAssetTags = Asset::whereIn(DB::raw('lower(asset_tag)'), array_change_key_case($assetTags, CASE_LOWER))->get()->pluck('asset_tag')->toArray();
        $existingSerialNumbers = Asset::whereIn(DB::raw('lower(serial_no)'), array_change_key_case($assetTags, CASE_LOWER))->get()->pluck('serial_no')->toArray();
        $existingAssets = array_merge($existingAssetTags, $existingSerialNumbers);
        $existingAssets =   array_change_key_case($existingAssets, CASE_LOWER);
        $notExistingAssets = array_diff(array_map('strtolower', $assetTags), array_map('strtolower', $existingAssets));
        $dataNotInDatabase = array();
        foreach ($notExistingAssets as $tag) {
            $discrepancy = 'Scanned but not present';
            $match = 'N';
            $error = 'Process Error';
            $dataNotInDatabase[] = $this->getNotInDbData($tag, $location['room_name'], $match, $discrepancy, $error);
        }

        return $dataNotInDatabase;
    }

    /**
     * Generating Not in the Database Asset Details to Show in Vairance Report generating page
     *
     * @param string  $assetTag     Asset Tag
     * @param string  $location     Location of the Asset
     * @param string  $match        Asset Matched/Not with location (Y or N)
     * @param string  $discrepancy  Descrepency string
     *
     * @return array()
     */
    public function getNotInDbData($assetTag, $location, $match, $discrepancy, $error)
    {
        return [
            'asset_tag' => $assetTag,
            'serial_no' => '',
            'asset_type' => '',
            'hardware_standard' => '',
            'technical_spec' => '',
            'status' => '',
            'location' => $location,
            'location_match' => $match,
            'discrepancy' => $discrepancy,
            'error' => $error,
        ];
    }

    /**
     * Returns the matched assets
     * @param mixed $assetTags
     * @param mixed $locationId
     * @param mixed $assetTypeId
     * 
     * @return [type]
     */
    public function getAssetsMatchedCount($assetTags, $locationId, $assetTypeId)
    {
        return AssetRepository::getAssetScannedInLocationCount($assetTags, $locationId, $assetTypeId);
    }

    /**
     * Returns the not matched assets
     * @param mixed $assetTags
     * @param mixed $locationId
     * @param mixed $assetTypeId
     * 
     * @return [type]
     */
    public function getAssetsNotMatchedCount($assetTags, $locationId, $assetTypeId)
    {
        return AssetRepository::getAssetNotScannedInLocationCount($assetTags, $locationId, $assetTypeId);
    }

    /**
     * Returns the assets scanned but in another location
     * @param mixed $assetTags
     * @param mixed $locationId
     * @param mixed $assetTypeId
     * 
     * @return [type]
     */
    public function getAssetsScannedButInAnotherLocationCount($assetTags, $locationId, $assetTypeId)
    {
        return AssetRepository::getAssetScannedButNotBelongsToTheSelectedLocationCount($assetTags, $locationId, $assetTypeId);
    }

    /**
     * Get discrepancy string of an asset after scanning
     *
     * @param object  $item   App\Models\Asset Object
     * @param string  $match  Conditions used for matched or not (Y or N)
     *
     * @return string
     */
    public function getDiscrepancy($item, $match)
    {
        $discrepancy = '';

        if ($match == 'N') {
            $statusName = $item->assetStatus ? $item->assetStatus['name'] : "";
            $discrepancy = 'Asset is currently ' . $statusName;
            if ($item->location) {
                $discrepancy = $discrepancy . ' at ' . $item->location['room_name'];
            }

            if ($item->user) {
                $discrepancy = $discrepancy . ' to ' . $item->user['first_name'] . ' ' . $item->user['last_name'];
            }
        }
        return $discrepancy;
    }
}
