<?php

namespace App\Services\Api;

use App\Models\Asset;
use App\Models\AssetType;
use App\Models\Location;
use App\Models\Carrier;
use App\Models\MakeAndModel;
use App\Models\TechnicalSpecs;
use Illuminate\Validation\Rule;
use Validator;
use Carbon\Carbon;
use App\Models\AssetStatus;
use App\Events\BulkUpdates;
use App\Models\AssetTracking;
use App\Models\Vendor;
use App\Rules\AssetTypeValidation;
use App\Rules\LocationExistsAndIsStorage;
use Facades\App\Services\Asset\AssetStatusService;
use Auth;
use App\Services\Integrations\Tickets\TicketManagementService;
use App\User;

/**
 * Class AssetCreateApiService
 *
 * Service class for creating and validating asset data from API requests.
 */
class AssetCreateApiService extends ApiService
{
    /**
     * @var TicketManagementService
     */
    private $ticketManagementService;

    /**
     * AssetCreateApiService constructor.
     *
     * @param TicketManagementService $ticketManagementService
     */
    public function __construct(TicketManagementService $ticketManagementService)
    {
        $this->ticketManagementService = $ticketManagementService;
    }

    /**
     * Get and prepare asset data from the request.
     *
     * @return array|bool Asset data array or false if validation fails.
     */
    public function getData()
    {
        $inputData = request()->all();

        $assetData = $this->initializeAssetData($inputData);

        $this->setAssetStatus($assetData);

        $relationalValues = ['asset_type', 'hardware_standard', 'technical_spec', 'location', 'carrier', 'asset_tracking_id'];

        $this->populateVendorData($inputData, $assetData);
        $this->populateLocationData($inputData, $assetData);
        $this->populateCarrierData($inputData, $assetData);
        $this->populateAssetTypeData($inputData, $assetData);
        $this->populateMakeAndModelData($inputData, $assetData);
        $this->populateTechnicalSpecsData($inputData, $assetData);
        $this->populateAssetTrackingData($inputData, $assetData);

        $hasRelationalValues = array_intersect_key(array_flip($relationalValues), $inputData);
        if (empty($hasRelationalValues)) {
            return $assetData;
        }

        $errors = $this->validateRelationalFields($assetData);
        if ($errors) {
            return false;
        }

        return $assetData;
    }

    /**
     * Validate the fields from the request.
     *
     * @return \Illuminate\Contracts\Validation\Validator|null Validation errors or null if validation succeeds.
     */
    public function validateFields()
    {
        $validator = Validator::make(
            request()->all(),
            [
                'serial_no'          => 'required|unique:assets,serial_no',
                'asset_tag'          => 'required_unless:asset_type,Mobile Phone Number|unique:assets,asset_tag',
                'po_id'              => 'required',
                'location'           => ['required', new LocationExistsAndIsStorage],
                'hardware_standard'  => 'required|exists:make_and_models,name',
                'technical_spec'     => 'required|exists:technical_specs,details',
                'asset_type'         => 'required|exists:asset_types,name',
                'carrier'            => 'nullable|exists:carriers,name',
                'created_at'         => ['nullable', 'date_format:' . config('date.formats.read_date_format')],
                'warranty_end_date'  => ['nullable', 'date_format:' . config('date.formats.read_date_format')],
                'lease_start_date'   => ['nullable', 'date_format:' . config('date.formats.read_date_format')],
                'lease_end_date'     => ['nullable', 'date_format:' . config('date.formats.read_date_format')],
                'lost_date'          => ['nullable', 'date_format:' . config('date.formats.read_date_format')],
                'imei'              => 'required_if:asset_type,Mobile'
            ],
            [
                'serial_no.required'          => 'The serial # is required.',
                'po_id.required'          => 'The PO # is required.',
                'location.required'          => 'The location is required.',
                'serial_no.exists'            => 'The serial # does not exists',
                'asset_tag.required_unless'          => 'The asset tag # is required.',
                'asset_tag.unique' => 'The asset tag # has already been taken.',
                'hardware_standard.exists'    => 'The hardware standard does not exist.',
                'technical_spec.exists'    => 'The technical specs does not exist.',
                'asset_type.exists'    => 'The asset type does not exist.',
                'carrier.exists'    => 'The carrier does not exist.',
                'created_at.date_format' => 'The created date format is incorrect. Use ' . config('date.formats.read_date_format') . ' format',
                'lost_date.date_format' => 'The lost date format is incorrect. Use ' . config('date.formats.read_date_format') . ' format',
                'lease_end_date.date_format' => 'The lease end date format is incorrect. Use ' . config('date.formats.read_date_format') . ' format',
                'lease_start_date.date_format' => 'The lease start date format is incorrect. Use ' . config('date.formats.read_date_format') . ' format',
                'warranty_end_date.date_format' => 'The warranty end date format is incorrect. Use ' . config('date.formats.read_date_format') . ' format',
                'imei.required_if'          => 'The IMEI # is required for mobile asset type.',
            ]
        );

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

    /**
     * Validate the relational fields.
     *
     * @param array $data Asset data to validate.
     *
     * @return \Illuminate\Contracts\Validation\Validator|null Validation errors or null if validation succeeds.
     */
    public function validateRelationalFields($data)
    {
        $rules = [
            'make_and_model_id' => 'required|exists:make_and_models,id',
            'technical_spec_id' => 'required|exists:technical_specs,id',
            'asset_type_id' => ['required', 'exists:asset_types,id', new AssetTypeValidation($data)],
        ];

        $validator = Validator::make($data, $rules);

        return $validator->fails() ? $validator->errors() : null;
    }

    /**
     * Save asset data to the database.
     *
     * @param array $assetData Asset data to be saved.
     *
     * @return void
     */
    public function saveData($assetData)
    {
        $asset = Asset::updateorCreate(['serial_no' => $assetData['serial_no']], $assetData);

        $this->saveAssetHistory($asset, $assetData);
    }

    /**
     * Save asset history information.
     *
     * @param Asset $asset Asset instance.
     * @param array $data Asset data.
     *
     * @return void
     */
    public function saveAssetHistory(Asset $asset, array $data)
    {
        $description = $this->generateAssetHistoryDescription($asset, $data);

        $assetHistory = [
            'user_id' => Auth::id(),
            'asset_id' => $asset->id,
            'ticket_no' => $data['ticket_no'],
            'ticket_service_provider' => config('ticket-integration.service'),
            'new_location_id' => $data['location_id'],
            '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));

        $this->updateJiraTicket($asset, $data);
    }

    /**
     * Generate the description for asset history.
     *
     * @param Asset $asset Asset instance.
     * @param array $data Asset data.
     *
     * @return string Description.
     */
    private function generateAssetHistoryDescription(Asset $asset, array $data)
    {
        $newRoomName = optional(Location::findOrFail($data['location_id']))->room_name;

        return __('history.Created', [
            'assetname' => $data['serial_no'],
            'assetid' => $asset->id,
            'newroomname' => $newRoomName,
        ]);
    }

    /**
     * Update the Jira ticket if a ticket number is provided.
     *
     * @param Asset $asset Asset instance.
     * @param array $data Asset data.
     *
     * @return void
     */
    private function updateJiraTicket(Asset $asset, array $data)
    {
        if (!empty($data['ticket_no'])) {
            $user = User::find(Auth::id());
            $jiraDescription = $this->generateJiraDescription($asset, $user);

            $jiraData[] = [
                'description' => $jiraDescription,
                'ticketId'   => $data['ticket_no'],
                'userId' => $user->id,
            ];

            $this->ticketManagementService->bulkAddComment($jiraData);
        }
    }

    /**
     * Generate the description for the Jira ticket update.
     *
     * @param Asset $asset Asset instance.
     * @param User $user User instance.
     *
     * @return string Description.
     */
    private function generateJiraDescription(Asset $asset, User $user)
    {
        return __('jira.Created', [
            'assetname' => $asset->serial_no,
            'assetid' => $asset->id,
            'username' => $user->first_name . ' ' . $user->last_name,
            'useremail' => $user->email,
        ]);
    }

    /**
     * Initialize asset data from the input data.
     *
     * @param array $inputData Input data.
     *
     * @return array Initialized asset data.
     */
    private function initializeAssetData($inputData)
    {
        $directValues = [
            'asset_tag',
            'po_id',
            'serial_no',
            'imei',
            'created_at',
            'warranty_end_date',
            'lease_start_date',
            'lease_end_date',
            'asset_original_value',
            'lost_date',
            'end_of_life_date'
        ];

        $assetData = [];

        foreach ($inputData as $key => $value) {
            if (in_array($key, $directValues)) {
                $assetData[$key] = $value;
            }
        }

        $assetData['ticket_no'] = $inputData['ticket_no'] ?? '';

        return $assetData;
    }

    /**
     * Set the asset status based on business logic.
     *
     * @param array $assetData Asset data.
     *
     * @return void
     */
    private function setAssetStatus(&$assetData)
    {
        $assetStatus = AssetStatusService::whenCreatingAsset();
        $assetStatusId = AssetStatus::where('slug', $assetStatus)->first()->id;
        $assetData['asset_status_id'] = $assetStatusId;
    }

    /**
     * Populate vendor data in the asset data array.
     *
     * @param array $inputData
     * @param array $assetData
     *
     * @return void
     */
    private function populateVendorData($inputData, &$assetData)
    {
        if (array_key_exists('vendor', $inputData)) {
            $vendor = Vendor::getFromName(request('vendor'))->first();
            $vendorId = $vendor ? $vendor->id : null;
            $assetData['vendor_id'] = $vendorId;
        }
    }

    /**
     * Populate location data in the asset data array.
     *
     * @param array $inputData
     * @param array $assetData
     *
     * @return void
     */
    private function populateLocationData($inputData, &$assetData)
    {
        if (array_key_exists('location', $inputData)) {
            $location = Location::getFromName(request('location'))->first();
            $locationId = $location ? $location->id : null;
            $assetData['location_id'] = $locationId;
        }
    }

    /**
     * Populate carrier data in the asset data array.
     *
     * @param array $inputData
     * @param array $assetData
     *
     * @return void
     */
    private function populateCarrierData($inputData, &$assetData)
    {
        if (array_key_exists('carrier', $inputData)) {
            $carrier = Carrier::getFromName(request('carrier'))->first();
            $carrierId = $carrier ? $carrier->id : null;
            $assetData['carrier_id'] = $carrierId;
        }
    }

    /**
     * Populate asset type data in the asset data array.
     *
     * @param array $inputData
     * @param array $assetData
     * @return void
     */
    private function populateAssetTypeData($inputData, &$assetData)
    {
        if (array_key_exists('asset_type', $inputData)) {
            $assetType = AssetType::getFromName(request('asset_type'))->first();
            $assetTypeId = $assetType ? $assetType->id : null;
            $assetData['asset_type_id'] = $assetTypeId;
        }
    }

    /**
     * Populate make and model data in the asset data array.
     *
     * @param array $inputData
     * @param array $assetData
     *
     * @return void
     */
    private function populateMakeAndModelData($inputData, &$assetData)
    {
        if (array_key_exists('hardware_standard', $inputData)) {
            $makeAndModel = MakeAndModel::where('name', request('hardware_standard'))->first();
            $makeAndModelId = $makeAndModel ? $makeAndModel->id : null;
            $assetData['make_and_model_id'] = $makeAndModelId;
        }
    }

    /**
     * Populate technical specs data in the asset data array.
     *
     * @param array $inputData
     * @param array $assetData
     *
     * @return void
     */
    private function populateTechnicalSpecsData($inputData, &$assetData)
    {
        if (array_key_exists('technical_spec', $inputData)) {
            $technicalSpecs = TechnicalSpecs::getTechnicalSpecs($assetData['make_and_model_id'])->where('details', request('technical_spec'))->first();
            $assetData['technical_spec_id'] = $technicalSpecs ? $technicalSpecs->id : null;
        }
    }

    /**
     * Populate asset tracking data in the asset data array.
     *
     * @param array $inputData
     * @param array $assetData
     *
     * @return void
     */
    private function populateAssetTrackingData($inputData, &$assetData)
    {
        if (array_key_exists('asset_tracking_id', $inputData) && request('asset_tracking_id') != "") {
            $assetData['asset_tracking_id'] = AssetTracking::firstOrCreate(['tracking_number' => request('asset_tracking_id')], ['tracking_number' => request('asset_tracking_id')])->id ?? '';
        }
    }
}
