<?php

namespace App\Services\Asn;

use App\Models\AsnAccessoriesMapping;
use App\Models\AsnHardwareMapping;
use App\Models\AsnUnwantedPart;
use App\Models\Asset;
use App\Models\AssetType;
use App\Models\MakeAndModel;
use App\Models\TechnicalSpecs;
use Exception;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

class AbstractAsnMappingService
{

    /**
     * Retrieve items from a file based on its extension.
     *
     * @param string $path The path to the file.
     *
     * @return array|bool The array of items if the file exists and is valid, otherwise false.
     */
    public function getItems($path)
    {
        if (!file_exists($path)) {
            return false;
        }

        $extension = getFileExtension($path);

        if ($extension == 'csv') {
            $items = csv_to_array($path);
        } elseif ($extension == 'xlsx') {
            $items = excel_to_array($path);
        }

        if (empty($items)) {
            return false;
        }

        return $items;
    }

    /**
     * Check if the headers of a file match the expected headers.
     *
     * @param string $path      The path to the file.
     * @param array  $headerMap An associative array mapping the expected headers.
     *
     * @return array The array of headers that are in the headerMap but not in the file's header.
     */
    public function checkHeader($path, $headerMap)
    {
        $extension = getFileExtension($path);

        if ($extension == 'csv') {
            $header = get_csv_header($path);
        } elseif ($extension == 'xlsx') {
            $header = get_excel_header($path);
        }

        return array_diff(array_values($headerMap), $header);
    }

    /**
     * Import hardware mapping data from a file.
     *
     * @param string $path     The path to the file.
     * @param string $provider The provider for the hardware mapping data, default is 'asn'.
     *
     * @return array An array of error data encountered during the import process.
     */
    public function importHardwareMappingData($path, $provider = 'asn')
    {
        $data = $this->getItems($path);
        $items = $data['csvData'];
        $count = 1;
        $errorData = [];
        $headerMap = Config('bulk-upload.asnHardwareMappingData');

        if (!empty($items)) {
            foreach ($items as $item) {
                if (!count($item)) {
                    continue;
                }

                // Skip empty columns.
                $count++;
                $csvData = $this->generateHardwareMappingCSVData($item, $count, $headerMap);

                if ($csvData['errorData']) {
                    $errorData[] = $csvData['errorData'];
                }

                if (!$csvData['errorData']) {
                    try {
                        AsnHardwareMapping::updateorCreate(['provider' => $provider, 'part_no' => $csvData['hardwareMappingData']['part_no']], $csvData['hardwareMappingData']);
                    } catch (Exception $e) {
                        //continue
                    }
                }
            }
        }

        return $errorData;
    }

    /**
     * Generate hardware mapping CSV data from an item.
     *
     * @param array $item      The CSV item data.
     * @param int   $count     The row count for tracking purposes.
     * @param array $headerMap The mapping of CSV headers to database fields.
     *
     * @return array An associative array containing 'hardwareMappingData' and 'errorData'.
     */
    public function generateHardwareMappingCSVData($item, $count, $headerMap)
    {
        if (empty($item)) {
            return false;
        }

        $hardwareMappingData = [];
        $errorData = [];
        $row = $item;

        foreach ($headerMap as $dbField => $fileHeader) {
            $result = $this->getRelationalValues($item, $dbField, $fileHeader);
            $row[$fileHeader] = $result;
            $hardwareMappingData[$dbField] = $row[$fileHeader];
        }

        $errorData = $this->validateFields($hardwareMappingData, $count);

        return compact('hardwareMappingData', 'errorData');
    }

    /**
     * Get relational values for an item based on the database field and file header.
     *
     * @param array  $item       The CSV item data.
     * @param string $dbField    The database field name.
     * @param string $fileHeader The CSV file header name.
     *
     * @return mixed The relational value based on the database field, or the original item value if no relation is found.
     */
    public function getRelationalValues($item, $dbField, $fileHeader)
    {
        if ($dbField == "make_and_model_id") {
            $hardware =  MakeAndModel::where('name', $item[$fileHeader])->first();

            return $hardware ? $hardware->id : 0;
        }

        if ($dbField == "technical_spec_id") {
            $hardware =  MakeAndModel::where('name', $item['Hardware Standard'])->first();

            if ($hardware) {
                $tech = TechnicalSpecs::with(['makeAndModel'])->where('make_and_model_id', $hardware->id)->where('details', $item[$fileHeader])->first();
            } else {
                $tech = TechnicalSpecs::where('details', $item[$fileHeader])->first();
            }

            return $tech ? $tech->id : 0;
        }

        return $item[$fileHeader];
    }


    /**
     * Validate the fields in the CSV data.
     *
     * @param array $csvData The CSV data to validate.
     * @param int   $count   The row count for tracking purposes.
     *
     * @return array The validation errors, if any.
     */
    public function validateFields($csvData, $count)
    {
        return $this->csvValidator($csvData, $count);
    }

    /**
     * Validate the CSV data.
     *
     * @param array $data  The CSV data to validate.
     * @param int   $count The row count for tracking purposes.
     *
     * @return array The validation errors, if any.
     */
    public function csvValidator($data, $count)
    {
        $validator = Validator::make(
            $data,
            [
                'part_no'               => 'required',
                'make_and_model_id'     => 'required|exists:make_and_models,id',
                'technical_spec_id'   => 'required|exists:technical_specs,id',
            ],
            [
                'part_no.required'              => 'Line no ' . $count . ' : The Part #  is required.',
                'make_and_model_id.exists'      => 'Line no ' . $count . ' : The Hardware standard does not exist.',
                'make_and_model_id.required'    => 'Line no ' . $count . ' : The Hardware standard is required.',
                'technical_spec_id.exists'      => 'Line no ' . $count . ' : The Tech spec for Hardware standard does not exist.',
                'technical_spec_id.required'    => 'Line no ' . $count . ' : The Tech spec is required.',
            ]
        );

        if ($validator->fails()) {
            return $validator->errors()->all();
        }
    }

    /**
     * Validate the accessory mapping CSV data.
     *
     * @param array $data  The CSV data to validate.
     * @param int   $count The row count for tracking purposes.
     *
     * @return array The validation errors, if any.
     */
    public function accessoryMappingCsvValidateFields($data, $count)
    {

        $accessoryType = AssetType::getBySlug('accessories')->first()->id;

        $validator = Validator::make(
            $data,
            [
                'part_no'          => ['required'],
                'make_and_model_id'          => ['required', Rule::exists('make_and_models', 'id')->where('asset_type_id', $accessoryType)]
            ],
            [
                'part_no.required' => 'Line no ' . $count . ' : The part #  is required.',
                'make_and_model_id.required' => 'Line no ' . $count . ' : The hardware standard  is required.',
                'make_and_model_id.exists' => 'Line no ' . $count . ' : The accessory  type does not exist.'
            ]
        );

        if ($validator->fails()) {
            return $validator->errors()->all();
        }
    }

    /**
     * Import accessory mapping data from a file.
     *
     * @param string $path     The path to the file.
     * @param string $provider The provider for the accessory mapping data, default is 'asn'.
     *
     * @return array An array of error data encountered during the import process.
     */
    public function importAccessoryMappingData($path, $provider = 'asn')
    {
        $data = $this->getItems($path);
        $items = $data['csvData'];
        $count = 1;
        $errorData = [];
        $headerMap = Config('bulk-upload.asnAccessoriesMappingData');

        if (!empty($items)) {
            foreach ($items as $item) {
                if (!count($item)) {
                    continue;
                }

                // Skip empty columns.
                $count++;

                $csvData = $this->generateAccessoryMappingCSVData($item, $count, $headerMap);

                if ($csvData['errorData']) {
                    $errorData[] = $csvData['errorData'];
                }

                if (!$csvData['errorData']) {
                    try {
                        AsnAccessoriesMapping::updateorCreate(['provider' => $provider, 'part_no' => $csvData['accessoriesMappingData']['part_no']], $csvData['accessoriesMappingData']);
                    } catch (Exception $e) {
                        //continue
                    }
                }
            }
        }

        return $errorData;
    }

    /**
     * Generate accessory mapping CSV data from an item.
     *
     * @param array $item      The CSV item data.
     * @param int   $count     The row count for tracking purposes.
     * @param array $headerMap The mapping of CSV headers to database fields.
     *
     * @return array An associative array containing 'accessoriesMappingData' and 'errorData'.
     */
    public function generateAccessoryMappingCSVData($item, $count, $headerMap)
    {
        if (empty($item)) {
            return false;
        }

        $errorData = [];
        $accessoriesMappingData = [];

        foreach ($headerMap as $dbField => $fileHeader) {
            $accessoriesMappingData[$dbField] = !empty($item[$fileHeader]) ? $item[$fileHeader] : null;

            if ($dbField == 'make_and_model_id') {
                $accessoriesMappingData[$dbField] = !empty($item[$fileHeader]) ? optional(MakeAndModel::where('name', $item[$fileHeader])->first())->id ?? 'x' : null;
            }
        }

        $errorData = $this->accessoryMappingCsvValidateFields($accessoriesMappingData, $count);

        return compact('accessoriesMappingData', 'errorData');
    }

    /**
     * Import unwanted parts mapping data from a file.
     *
     * @param string $path     The path to the file.
     * @param string $provider The provider for the unwanted parts mapping data, default is 'asn'.
     *
     * @return array An array of error data encountered during the import process.
     */
    public function importUnwantedPartsMappingData($path, $provider = 'asn')
    {
        $data = $this->getItems($path);
        $items = $data['csvData'];
        $count = 1;
        $errorData = [];
        $headerMap = Config('bulk-upload.asnUnwantedParts');

        if (!empty($items)) {
            foreach ($items as $item) {
                if (!count($item)) {
                    continue;
                }

                // Skip empty columns.
                $count++;
                $csvData = $this->generateUnwantedPartAccessoryMappingCSVData($item, $count, $headerMap);

                if ($csvData['errorData']) {
                    $errorData[] = $csvData['errorData'];
                }

                if (!$csvData['errorData']) {
                    try {
                        AsnUnwantedPart::updateorCreate(['provider' => $provider, 'part_no' => $csvData['unwantedPartsMappingData']['part_no']], $csvData['unwantedPartsMappingData']);
                    } catch (Exception $e) {
                        //continue
                    }
                }
            }
        }

        return $errorData;
    }

    /**
     * Generate unwanted part accessory mapping CSV data from an item.
     *
     * @param array $item      The CSV item data.
     * @param int   $count     The row count for tracking purposes.
     * @param array $headerMap The mapping of CSV headers to database fields.
     *
     * @return array An associative array containing 'unwantedPartsMappingData' and 'errorData'.
     */
    public function generateUnwantedPartAccessoryMappingCSVData($item, $count, $headerMap)
    {
        if (empty($item)) {
            return false;
        }

        $errorData = [];
        $unwantedPartsMappingData = [];

        foreach ($headerMap as $dbField => $fileHeader) {
            $unwantedPartsMappingData[$dbField] = !empty($item[$fileHeader]) ? $item[$fileHeader] : null;
        }

        $errorData = $this->unwantedPartsMappingCsvValidateFields($unwantedPartsMappingData, $count);

        return compact('unwantedPartsMappingData', 'errorData');
    }

    /**
     * Validate the unwanted parts mapping CSV data.
     *
     * @param array $data  The CSV data to validate.
     * @param int   $count The row count for tracking purposes.
     *
     * @return array The validation errors, if any.
     */
    public function unwantedPartsMappingCsvValidateFields($data, $count)
    {
        $validator = Validator::make(
            $data,
            [
                'part_no'          => ['required'],
            ],
            [
                'part_no.required' => 'Line no ' . $count . ' : The part #  is required.',
            ]
        );

        if ($validator->fails()) {
            return $validator->errors()->all();
        }
    }

    /**
     * Resynchronize individual hardware parts by updating their make and model, technical specs, and asset type.
     *
     * @param int|array $assetStatusId  The asset status ID(s) to filter assets.
     * @param string    $partNo         The part number to filter assets.
     * @param int|null  $makeAndModelId The ID of the make and model to set, default is null.
     * @param int|null  $techSpecs      The ID of the technical specifications to set, default is null.
     *
     * @return bool Returns true if the update is successful.
     */
    public function individualHardwarePartResync($assetStatusId, $partNo, $makeAndModelId = null, $techSpecs = null)
    {
        $makeAndModel  = MakeAndModel::findOrFail($makeAndModelId);

        Asset::whereIn('asset_status_id', $assetStatusId)
            ->where(['part_no' => $partNo, 'make_and_model_id' => 0, 'technical_spec_id' => 0])
            ->update([
                'make_and_model_id' => $makeAndModelId,
                'technical_spec_id' => $techSpecs,
                'asset_type_id'     => $makeAndModel->asset_type_id
            ]);

        return true;
    }

    /**
     * Resynchronize hardware parts by resetting their make and model, technical specs, and asset type upon deletion.
     *
     * @param int|array          $assetStatus        The asset status ID(s) to filter assets.
     * @param AsnHardwareMapping $asnHardwareMapping The ASN hardware mapping object containing part details.
     *
     * @return bool Returns true if the update is successful.
     */
    public function resyncHardwarePartOnDelete($assetStatus, $asnHardwareMapping)
    {
        Asset::whereIn('asset_status_id', $assetStatus)
            ->where(['part_no' => $asnHardwareMapping->part_no, 'make_and_model_id' => $asnHardwareMapping->make_and_model_id, 'technical_spec_id' => $asnHardwareMapping->technical_spec_id])
            ->update([
                'make_and_model_id' => 0,
                'technical_spec_id' => 0,
                'asset_type_id'     => 0
            ]);

        return true;
    }

     /**
     * Run the validator and retun the errors
     */
    public function validateHardwareMapping()
    {
        $validator = Validator::make(
            request()->all(),
            [
                'part_no'           => 'required',
                'make_and_model'    => 'required',
                'technical_spec'    => 'required',
            ],
            $messages = [
                'part_no.required'          => 'The Part # is required.',
                'make_and_model.required'   => 'The Hardware Standard is required.',
                'technical_spec.required'   => 'The Tech Specs is required.',
            ]
        );

        if ($validator->fails()) {
            return $validator->errors()->all();
        }

        return false;
    }
}
