<?php

namespace App\Services\Reports;

use Carbon\Carbon;
use App\Repositories\CommonFilterRepository;
use App\Http\Responses\ReportOutputData;
use App\Repositories\Reports\DamagedTrendsRepository;
use App\Services\ReportGraphAndDataExport;
use Illuminate\Support\Facades\Storage;
use Maatwebsite\Excel\Facades\Excel;

class DamagedTrendsService extends AbstractReportService
{
    protected $repository;
    protected $commonFilterRepo;
    protected $reportOutputData;

    /**
     * Constructor for the class.
     *
     * @param DamagedTrendsRepository $repository description
     * @param CommonFilterRepository $commonFilterRepo description
     * @param ReportOutputData $reportOutputData description
     */
    public function __construct(DamagedTrendsRepository $repository, CommonFilterRepository $commonFilterRepo, ReportOutputData $reportOutputData)
    {
        $this->repository = $repository;
        $this->commonFilterRepo = $commonFilterRepo;
        $this->reportOutputData = $reportOutputData;
    }

    /**
     * Retrieves the filter data options including asset types, make and models, locations, and departments.
     *
     * @return array The filter data options.
     */
    public function getFilterDataOptions()
    {
        return $this->repository->getFilters();
    }

    /**
     * Generate graph data based on the given filter.
     *
     * @param bool $isLaptopOnlyFilter Indicates whether to filter only for laptops
     * @return array Returns compact data, months, selected make and models, and asset list
     */
    public function generateGraphData($isLaptopOnlyFilter)
    {
        $inputData = $this->getInputData();

        //only laptop -  logic
        if (!$isLaptopOnlyFilter) {
            $requestedMakeAndModelIds = $inputData['make_and_model'];
            $selectedMakeAndModels = $this->repository->getMakeAndModelsByIds($requestedMakeAndModelIds);
        } else {
            $selectedMakeAndModels = $this->getLaptopMakeAndModels(config('reports.damaged_trends.laptop_asset_types'));
            $requestedMakeAndModelIds = $selectedMakeAndModels->pluck('id')->toArray();
        }

        $toDate = $this->getToDate($inputData['date-range']);
        $fromDate = $this->getFromDate($inputData['date-range'], $this->getToDate($inputData['date-range']));

        if ($inputData['date-range'] == 'last-30-days' || $inputData['date-range'] == 'last-60-days') {
            $months = get_past_month_names_day($fromDate, $this->getToDate(request('date-range')));
        } else {
            $months = get_past_month_names($fromDate, $this->getToDate(request('date-range')));
        }
        $fromDate = $this->getFromDate($inputData['date-range'], $this->getToDate($inputData['date-range']));

        if ($selectedMakeAndModels) {
            foreach ($selectedMakeAndModels as $makeAndModel) {
                $assets = $this->getData($makeAndModel->id, $fromDate, $toDate, $inputData)->get();
                $assets = group_by_months_history($assets);
                $datas[$makeAndModel->makeModelName] = $this->getFormattedData($assets, $months);
            }
        }

        // Graph representation formatting Laptops without Hardware standards
        if ($isLaptopOnlyFilter) {
            $results = [
                'Laptops' => [
                    0 => 0,
                    1 => 0,
                    2 => 0,
                    3 => 0,
                    4 => 0,
                    5 => 0,
                    6 => 0,
                    7 => 0,
                    8 => 0,
                    9 => 0,
                    10 => 0,
                    11 => 0,
                ]
            ];

            foreach ($datas as $singleData) {
                for ($i = 0; $i < 12; $i++) {
                    $results['Laptops'][$i] += $singleData[$i] ?? 0;
                }
            }

            $datas = $results;
            $selectedMakeAndModels = [
                (object)[
                    'makeModelName' => "Laptops"
                ]
            ];
        }
        //end  Graph representation formatting
        $assetList = $this->getData($requestedMakeAndModelIds, $fromDate, $toDate, $inputData)->distinct()->count();

        return compact('datas', 'months', 'selectedMakeAndModels', 'assetList');
    }


    public function getData($makeModelId, $fromDate, $toDate, $inputData)
    {
        $assets = $this->repository->getData();
        $assets = $this->commonFilterRepo->filterWithWhereHasRelationFields($assets, 'asset', 'make_and_model_id', $makeModelId);
        $assets = $this->commonFilterRepo->filterWithWhereHasRelationFields($assets, 'asset','asset_type_id', $inputData['asset_type']);

        if ($inputData['report_type'] == 'location') {
            $assets = $this->commonFilterRepo->filterWithWhereHasRelationFields($assets, 'asset', 'location_id', $inputData['types_selected_ids']);
        }

        if ($inputData['report_type'] == 'department') {
            $assets = $this->commonFilterRepo->filterWithNestedWhereHasRelationFields($assets, 'asset', 'user', 'department_id', $inputData['types_selected_ids']);
        }

        if ($fromDate) {
            $assets = $this->commonFilterRepo->filterWithDirectDateRange($assets, 'created_at', parse_date_from_db_datetime($fromDate), parse_date_from_db_datetime($toDate));
        }
        $assets = $this->commonFilterRepo->filterWithNestedWhereHasRelationFields($assets, 'asset', 'makeAndModel', 'manufacturer_id', $inputData['manufacturer']);

        return $assets;
    }

    /**
     * A function to retrieve data and process it for output.
     *
     * @return array
     */
    public function data()
    {
        $inputData = $this->getInputData();
        $isLaptopOnlyFilter = $this->isLaptopOnlyFilter($inputData);
        $requestedMakeAndModelIds = $isLaptopOnlyFilter ? $this->getLaptopMakeAndModels(config('reports.damaged_trends.laptop_asset_types'))->pluck('id')->toArray() : $inputData['make_and_model'];
        $toDate = $this->getToDate($inputData['date-range']);
        $fromDate = $this->getFromDate($inputData['date-range'], $this->getToDate($inputData['date-range']));

        if ($inputData['month']) {
            $fromDate = $this->getFromDateByMonth($inputData['month']);
            $toDate = $this->getToDateByMonth($inputData['month']);
        }
        $result = $this->getData($requestedMakeAndModelIds, $fromDate, $toDate, $inputData)->distinct();
        $count  = $result->count();
        $result = $this->reportOutputData->getOutputData($result, ['id' => 'desc']);

        return compact('result', 'count');
    }

    /**
     * Generate data for export
     *
     * @return mixed
     */
    public function exportData()
    {
        $inputData = $this->getInputData();
        $isLaptopOnlyFilter = $inputData['report_type'] == 'asset_type' ? $this->isLaptopOnlyFilter($inputData) : false;
        $requestedMakeAndModelIds = $isLaptopOnlyFilter ? $this->getLaptopMakeAndModels(config('reports.damaged_trends.laptop_asset_types'))->pluck('id')->toArray() : $inputData['make_and_model'];
        $toDate = $this->getToDate($inputData['date-range']);
        $fromDate = $this->getFromDate($inputData['date-range'], $this->getToDate($inputData['date-range']));
        $assets = $this->getData($requestedMakeAndModelIds, $fromDate, $toDate, $inputData)->distinct();
        $assets = $assets->orderBy('id', 'desc');

        return $assets;
    }

    /**
     * Retrieves input data based on the provided request data.
     *
     * @return array
     */
    public function getInputData(): array
    {
        $requestData = request()->form ?? request()->all();

        return [
            'report_type' => $requestData['report_type'] ?? '',
            'types_selected_ids' => $requestData[$requestData['report_type']] ?? '',
            'asset_type' => $requestData['asset_type'] ?? '',
            'make_and_model' => $requestData['make_and_model'] ?? '',
            'date-range' => $requestData['date-range'] ?? '',
            'from-date' => $requestData['from-date'] ?? '',
            'to-date' => $requestData['to-date'] ?? '',
            'month' => $requestData['month'] ?? '',
            'manufacturer' => $requestData['manufacturer'] ?? '',
        ];
    }

    /**
     * Retrieves the nested data for a given item.
     *
     * @param mixed $item The item for which to retrieve the nested data.
     * @param int $index The index of the item.
     * @return array The nested data for the item.
     */
    public function getNestedData($item, $index)
    {
        $commonData = $this->getReportNestedData($item);
        $nestedData = $commonData['report'];
        $nestedData['id'] = $index;
        $nestedData['asset_tag'] = generateAssetLink($item->asset->id, $nestedData['asset_tag']);
        $nestedData['serial_no'] = generateAssetLink($item->asset->id, $nestedData['serial_no']);
        $nestedData['user_location'] = $item->asset->user ? generateUserLink($item->asset->user->id, $nestedData['user_location']) : ($item->asset->location ? generateLocationLink($item->asset->location->id, $nestedData['user_location']) : '');

        return $nestedData;
    }

    /**
     * Get nested data for the report.
     *
     * @param datatype $asset description of the asset parameter
     * @return array the nested data for the report
     */
    public function getReportNestedData($assetHistory)
    {
        $nestedData = [];
        $nestedData['report']['asset_tag']              = $nestedData['export']['Asset Tag #']            = disableCSVInjection($assetHistory->asset->asset_tag);
        $nestedData['report']['serial_no']              = $nestedData['export']['Serial #']               = disableCSVInjection($assetHistory->asset->serial_no);
        $nestedData['report']['asset_type']             = $nestedData['export']['Asset Type']             = disableCSVInjection($assetHistory->asset->assetType->name ?? '');
        $nestedData['report']['asset_status']           = $nestedData['export']['Asset Status']           = disableCSVInjection($assetHistory->asset->assetStatus->name ?? '');
        $nestedData['report']['hardware_standard']      = $nestedData['export']['Hardware Standard']      = disableCSVInjection($assetHistory->asset->makeAndModel->makeModelName ?? '');
        $nestedData['report']['technical_specs']        = $nestedData['export']['Technical Specs']        = disableCSVInjection($assetHistory->asset->technicalSpec->details ?? '');
        $nestedData['report']['user_location']          = $nestedData['export']['User/Location']          = disableCSVInjection($assetHistory->asset->user ? ($assetHistory->asset->user->userName) : ($assetHistory->asset->location ? $assetHistory->asset->location->room_name : ''));
        $nestedData['report']['damaged_at']             = $nestedData['export']['Damaged Date']           = disableCSVInjection($assetHistory->CreatedDateLabel ?? '');

        return $nestedData;
    }

    /**
	 * Export the graph and data list to Excel.
	 *
	 * @param array $dataCollection The collection of data.
	 */
	public function exportGraphAndDataListToExcel(array $dataCollection)
	{
		$name = str_replace(' ', '_', request('name'));
		$name = str_replace('/', '_', $name);
		$sheetTitle = str_replace(' ', '_', request('sheettitle'));
		$sheetTitle = str_replace('/', '_', $sheetTitle);

		$headings = array_keys($dataCollection[0][0]);
		$image = storage_path('app/' . request('image'));
		$filename = request("filename") . time() . '.xlsx';
		$excel = Excel::download(new ReportGraphAndDataExport($dataCollection, $image, $headings, $sheetTitle), $filename, \Maatwebsite\Excel\Excel::XLSX);
		$img = explode('public/', request('image'));
		Storage::disk('public')->delete($img[1]);

		return $excel;
	}

    /**
     * Get formatted data from assets based on months.
     *
     * @param mixed $assets The assets to be formatted
     * @param array $months The months to base the formatting on
     * @return array The formatted data
     */
    public function getFormattedData($assets, $months)
    {
        $formattedData = [];

        foreach ($months as $monthNumber => $monthName) {
            $formattedData[] = is_countable($assets->get($monthNumber)) ? count($assets->get($monthNumber)) : 0;
        }

        return $formattedData;
    }

    /**
     * Checks if the input data corresponds to a laptop-only filter.
     *
     * @param array $inputData The input data to be checked
     * @return bool Returns true if the input data corresponds to a laptop-only filter, otherwise false
     */
    public function isLaptopOnlyFilter($inputData)
    {
        $laptopAssetTypeId = $this->repository->getLaptopAssetId();

        if (is_array($inputData['asset_type'])) {
            return false;
        }

        return $inputData['asset_type'] == $laptopAssetTypeId && empty($inputData['make_and_model']) ? true : false;
    }

    /**
     * Get the make and models of laptops for the given asset types.
     *
     * @param datatype $assetTypes description of the asset types
     * @return Collection
     */
    public function getLaptopMakeAndModels($assetTypes)
    {
        return $this->repository->getLaptopMakeAndModels($assetTypes);
    }

    /**
     * Get the "to" date based on the given date range.
     *
     * @param string $dateRange The date range to determine the "to" date
     * @return Carbon The "to" date
     */
    public function getToDate($dateRange)
    {
        $toDate = Carbon::now();

        if ($dateRange == 'last-month') {
            $toDate = new Carbon('last day of last month');
        }

        if ($dateRange == 'custom') {
            $inputData = $this->getInputData();
            $toDate = $inputData['to-date'];
            $toDate = format_date_to_carbon($toDate);
        }

        return $toDate;
    }

    /**
     * Get the from date based on the given date range and to date.
     *
     * @param string $dateRange The date range identifier
     * @param Carbon $toDate The to date
     * @return Carbon The calculated from date
     */
    public function getFromDate($dateRange, $toDate)
    {
        if ($dateRange == 'lifetime') {
            $fromDate = first_day_current_month_last_year();
        }

        if ($dateRange == 'last-30-days') {
            $fromDate = $toDate->subDays(30);
        }
        if ($dateRange == 'last-60-days') {
            $fromDate = $toDate->subDays(60);
        }
        if ($dateRange == 'this-month') {
            $fromDate = new Carbon('first day of this month');
        }
        if ($dateRange == 'last-month') {
            $fromDate = new Carbon('first day of last month');
        }
        if ($dateRange == 'this-quarter') {
            $fromDate = $toDate->subQuarter();
        }
        if ($dateRange == 'custom') {
            $inputData = $this->getInputData();
            $fromDate = $inputData['from-date'];
            $fromDate = format_date_to_carbon($fromDate);
        }

        return $fromDate;
    }

    /**
     * Retrieves the start of the month from a given date.
     *
     * @param string $date The date in the format "M Y".
     * @return Carbon|null The last day of the month as a Carbon object, or null if $date is empty
     */
    public function getFromDateByMonth($date)
    {
        if (!empty($date)) {
            $dateFrom = Carbon::createFromFormat("M Y", $date)->startOfMonth();

            return $dateFrom;
        }

        return '';
    }

    /**
     * Get the last day of the month for the given date.
     *
     * @param string $date The date in "M Y" format
     * @return Carbon|null The last day of the month as a Carbon object, or null if $date is empty
     */
    public function getToDateByMonth($date)
    {
        if (!empty($date)) {
            $dateFrom = Carbon::createFromFormat("M Y", $date)->endOfMonth();

            return $dateFrom;
        }

        return '';
    }
}
