<?php

namespace App\Services\Asset;

use App\Events\BulkUpdates;
use App\Models\Asset;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Validator;

class UpdateAssetAttributeService
{

    /**
     * Validates the given data against the specific attribute's rules.
     *
     * @param  array  $data
     * @return \Illuminate\Http\JsonResponse
     */
    public function validateData($data)
    {
        $attribute = $data['attribute']; // The attribute to update (e.g., 'asset_tag', 'serial_no')
        $value = $data['value'] ?? null; // The new value for the attribute

        $leaseStartDate = null;

        if (in_array($attribute, ['lease_end_date'])) {
            $asset = Asset::select('lease_start_date', 'lease_end_date')->find($data['id']);
            $leaseStartDate = $asset->lease_start_date;
        }

        $validationRules = [
            'asset_tag' => ['required', Rule::unique('assets')->ignore($data['id']), 'regex:/^[a-zA-Z0-9\-\_\(\)\+\/\# ]+$/'],
            'serial_no' => ['required', Rule::unique('assets')->ignore($data['id']), 'regex:/^[a-zA-Z0-9\-\_\(\)\+\/\# ]+$/'],
            'asset_original_value' => ['nullable', 'integer'],
            'warranty_end_date' => ['nullable', 'date', 'date_format:' . config('date.formats.read_date_format')],
            'lease_start_date' => ['nullable', 'date', 'date_format:' . config('date.formats.read_date_format')],
            'lease_end_date' => ['nullable', 'date', 'date_format:' . config('date.formats.read_date_format'), 'after_or_equal:' . $leaseStartDate],
            'loaner_return_date' => ['nullable', 'date', 'date_format:' . config('date.formats.read_date_format')],
            'lock_status_id' => ['nullable', 'exists:lock_statuses,id'],
            'lock_notes' => ['nullable', 'regex:/^[a-zA-Z0-9\-\_\(\)\+\/\# ]+$/'],
            'unlock_code' => ['nullable', 'regex:/^[a-zA-Z0-9\-\_\(\)\+\/\# ]+$/'],
            'carrier_id' => ['nullable', 'exists:carriers,id'],
        ];

        // Check if the attribute has defined validation rules
        if (!isset($validationRules[$attribute])) {
            return response()->json(['error' => 'Invalid attribute specified.'], 400);
        }

        // Dynamically validate the specific attribute
        $dynamicValidation = Validator::make(
            [$attribute => $value], // Data to validate
            [$attribute => $validationRules[$attribute]] // Rules for the specific attribute
        );

        if ($dynamicValidation->fails()) {
            return response()->json(['errors' => $dynamicValidation->errors()], 422);
        }

        return true;
    }
    /**
     * Update the asset attribute.
     * 
     * @param array $inputData The data to update the asset attribute.
     *
     * @return array The updated asset data.
     */
    public function updateAssetAttribute($inputData)
    {
        $asset = Asset::find($inputData['id']);
        $attribute = $inputData['attribute'];

        $data = $this->getAttributeData($asset, $attribute, $inputData);

        $updatedData = $this->updateAssetAndCreateHistory($asset, $attribute, $data[$attribute]);

        return $updatedData;
    }

    /**
     * Returns array of attribute data including old value, new value and display name
     * @param Asset $asset
     * @param string $attribute name of asset attribute
     * @param array $inputData
     * @return array
     */
    public function getAttributeData($asset, $attribute, $inputData)
    {
        $data = array();

        if (!$asset) {
            return $data;
        }
        $oldValue = $this->getAttributeValue($asset, $attribute);
        $newValue = $inputData['value'];

        $data[$attribute] = [
            'name' => config('asset-attributes.attributes')[$attribute] ?? '',
            'old_value' => $oldValue,
            'new_value' => $newValue,
        ];

        return $data;
    }

    /**
     * Updates attribute value of asset and creates history for it.
     * @param Asset $asset
     * @param string $attribute name of asset attribute
     * @param array $data attribute details
     * @return array
     */
    public function updateAssetAndCreateHistory($asset, $attribute, $data)
    {
        if ($data['old_value'] == $data['new_value']) {
            return;
        }
        $asset->$attribute = $data['new_value'];
        $asset->save();
        $asset->refresh();
        $newValue = $this->getAttributeValue($asset, $attribute);

        return $this->createAssetHistoryForAttributeUpdate($asset, $data['name'], $data['old_value'], $newValue);
    }

    /**
     * creates history for asset attribute update
     *
     * @param Asset $asset
     * @param string $attribute attribute name of asset which was updated
     * @param string $oldValue old value of asset attribute
     * @param string $newValue new value of asset attribute
     *
     * @return void
     */
    public function createAssetHistoryForAttributeUpdate($asset, $attribute, $oldValue, $newValue)
    {
        if ($asset === null) {
            return;
        }

        $action = $this->determineAction($attribute);
        [$oldValueText, $newValueText] = $this->resolveRelationalValues($attribute, $oldValue, $newValue);

        $description = $this->generateDescription($asset, $attribute, $oldValueText, $newValueText);

        $this->createAssetHistory($asset, $action, $oldValue, $newValue, $description);

        return $this->prepareResponseData($attribute, $oldValueText, $newValueText);
    }

    /**
     * Determine the action name to use when creating asset history for an attribute update
     * 
     * @param string $attribute The name of the attribute being updated
     * 
     * @return string The action name
     */
    private function determineAction($attribute)
    {
        return array_key_exists($attribute, config('asset-attributes.attributeUpdateActions'))
            ? config('asset-attributes.attributeUpdateActions.' . $attribute)
            : 'assetAttributes_changed';
    }

    /**
     * Resolves relational values for an attribute, if the attribute has a relational model defined
     * in the `asset-attributes.relationalAttributes` config.
     * 
     * @param string $attribute The name of the attribute being resolved
     * @param mixed $oldValue The old value of the attribute
     * @param mixed $newValue The new value of the attribute
     * 
     * @return array An array containing the resolved old and new values
     */
    private function resolveRelationalValues($attribute, $oldValue, $newValue)
    {
        // We cannot directly show some attribute values directly in the history for example lock code
        if (in_array($attribute, config('asset-attributes.maskedAttributes'))) {
            return ['XXXX', 'XXXX'];
        }

        if (!array_key_exists($attribute, config('asset-attributes.relationalAttributes'))) {
            return [$oldValue, $newValue];
        }

        $relationConfig = config('asset-attributes.relationalAttributes.' . $attribute);
        $modelClass = $relationConfig['model'];
        $dbColumn = $relationConfig['db_column'];

        $resolvedOldValue = $oldValue ? $modelClass::where('id', $oldValue)->value($dbColumn) : null;
        $resolvedNewValue = $newValue ? $modelClass::where('id', $newValue)->value($dbColumn) : null;

        return [$resolvedOldValue, $resolvedNewValue];
    }

    /**
     * Generate the description for an asset attribute value update history record.
     *
     * @param Asset $asset The asset being updated
     * @param string $attribute The name of the attribute being updated
     * @param mixed $oldValue The old value of the attribute
     * @param mixed $newValue The new value of the attribute
     *
     * @return string The description for the history record
     */
    private function generateDescription($asset, $attribute, $oldValue, $newValue)
    {
        return __('history.AssetAttributeValueUpdated', [
            'attribute' => $attribute,
            'assetId'  => $asset->id,
            'assetName' => $asset->serial_no,
            'oldValue' => $oldValue ? 'from ' . $oldValue : '',
            'newValue' => $newValue,
        ]);
    }

    /**
     * Creates an asset history record and triggers a BulkUpdates event.
     *
     * @param Asset $asset The asset being updated.
     * @param string $action The action being recorded (e.g., 'updated', 'created').
     * @param mixed $oldValue The previous value of the asset attribute.
     * @param mixed $newValue The new value of the asset attribute.
     * @param string $description A description of the update event.
     *
     * @return void
     */

    private function createAssetHistory($asset, $action, $oldValue, $newValue, $description)
    {
        $assetHistory = [
            'user_id' => Auth::id(),
            'asset_id' => $asset->id,
            'action' => $action,
            'ticket_service_provider' => config('ticket-integration.service'),
            'old_value' => $oldValue,
            'new_value' => $newValue,
            'description' => $description,
            'created_at' => Carbon::now()->format('Y-m-d H:i:s'),
            'updated_at' => Carbon::now()->format('Y-m-d H:i:s'),
        ];

        event(new BulkUpdates($assetHistory));
    }

    /**
     * Prepare the response data for an asset attribute value update.
     *
     * @param string $attribute The name of the attribute being updated.
     * @param mixed $oldValue The previous value of the attribute.
     * @param mixed $newValue The new value of the attribute.
     *
     * @return array The response data.
     */
    private function prepareResponseData($attribute, $oldValue, $newValue)
    {

        $data = [
            'name' => $attribute ?? '',
            'old_value' => $oldValue,
            'new_value' => $newValue,
        ];

        return $data;
    }

    /**
     * Retrieves the value of the specified attribute from the given asset object.
     *
     * @param mixed $asset The asset object from which to retrieve the attribute value.
     * @param string $attributeName The name of the attribute whose value should be retrieved.
     * @return mixed The value of the specified attribute, or null if the attribute does not exist.
     */
    public function getAttributeValue($asset, $attributeName)
    {
        $value = $asset->$attributeName ?? null;

        return $value;
    }

    /**
     * Updates hardware and specifications for an asset.
     *
     * @param array $data An associative array containing the asset ID and the
     * attributes to be updated, where the keys are the attribute names and the
     * values are the new values for the attributes.
     *
     * @throws Exception If an error occurs during the update process.
     *
     * @return bool Whether the update was successful or not.
     */
    public function updateHardwareAndSpecs($data)
    {
        try {
            $assetId = $data['asset_id'];
            unset($data['asset_id']);

            foreach ($data as $attribute => $value) {
                $attributeArray = [
                    'id' => $assetId,
                    'attribute' => $attribute,
                    'value' => $value
                ];

                $this->updateAssetAttribute($attributeArray);
            }

            return true;
        } catch (Exception $e) {

            return false;
        }
    }
}
