<?php

namespace App\Repositories\AssetsHealth;

use App\Models\Asset;
use App\Models\AssetHealthAssetType;
use App\Models\AssetStatus;
use App\Repositories\DiscoveryTools\ChromebookRepository;
use App\Repositories\DiscoveryTools\IntuneRepository;
use App\Repositories\DiscoveryTools\WorkspaceOneRepository;
use App\Repositories\DiscoveryTools\MobileIronRepository;
use App\Repositories\DiscoveryTools\JamfRepository;
use App\Repositories\DiscoveryTools\KandjiRepository;
use Carbon\Carbon;
use DB;

class AssetsHealthReportRepository
{
    protected $assetTypeIds;

    /**
     * Constructor for the AssetsHealthReportRepository class.
     *
     * @param WorkspaceOneRepository $workspaceRepository
     * @param ChromebookRepository $chromebookRepository
     * @param IntuneRepository $intuneRepository
     * @param JamfRepository $jamfRepository
     * @param KandjiRepository $kandjiRepository
     * @param MobileIronRepository $mobileIronRepository
     */
    public function __construct(
        protected WorkspaceOneRepository $workspaceRepository,
        protected ChromebookRepository $chromebookRepository,
        protected IntuneRepository $intuneRepository,
        protected JamfRepository $jamfRepository,
        protected KandjiRepository $kandjiRepository,
        protected MobileIronRepository $mobileIronRepository,
    ) {
        $this->assetTypeIds = AssetHealthAssetType::query()->pluck('type_id')->toArray();
    }

    /**
     * Retrieves the end-of-life assets that do not have a certificate.
     *
     * @return null|\Illuminate\Database\Eloquent\Builder The end-of-life assets without a certificate.
     */
    public function getEndOfLifeAssetsWithoutCertificate()
    {
        $endOfLifeAssets = Asset::endOfLifeWithoutCertificate();

        if ($endOfLifeAssets) {
            $endOfLifeAssets = $endOfLifeAssets->whereIn('asset_type_id', $this->assetTypeIds);
        }

        return $endOfLifeAssets;
    }

    /**
     * Retrieves the assets that have reached the end of life and have their data wiped.
     *
     * @return null|\Illuminate\Database\Eloquent\Builder The assets that have reached end of life and have been wiped clean.
     */
    public function getEndOfLifeWipedCheckingIn()
    {
        $endOfLifeStatusId = AssetStatus::getStatusID('end_of_life_data_wiped')->first()->id;
        $assets = $this->getLastseenByStatus($endOfLifeStatusId);

        if ($assets) {
            $assets = $assets->whereIn('asset_type_id', $this->assetTypeIds);
        }

        return $assets;
    }

    /**
     * Retrieves all the lost or stolen assets that are not locked.
     *
     * @return null|\Illuminate\Database\Eloquent\Builder
     */
    public function getLostOrStolenNoLocked()
    {
        $statusId = AssetStatus::getStatusID('stolen_lost')->first()->id;

        $assets = Asset::where('asset_status_id', $statusId)
            ->whereNull('lock_code');

        $assets = $assets->whereIn('asset_type_id', $this->assetTypeIds);

        return $assets;
    }

    /**
     * Retrieves the list of lost or stolen checking-in assets.
     *
     * @return null|\Illuminate\Database\Eloquent\Builder The list of lost or stolen checking-in assets.
     */
    public function getLostOrStolenCheckingIn()
    {
        $lostOrStolenStatusId = AssetStatus::getStatusID('stolen_lost')->first()->id;
        $assets = $this->getLastseenByStatus($lostOrStolenStatusId);

        if ($assets) {
            $assets = $assets->whereIn('asset_type_id', $this->assetTypeIds);
        }

        return $assets;
    }

    /**
     * Retrieves the assets that are currently under legal hold and have not been backed up.
     *
     * @return null|\Illuminate\Database\Eloquent\Builder
     */
    public function getLegalHoldNotBackedUp()
    {
        $legalHoldStatusId = AssetStatus::getStatusID('legal_hold')->first()->id;
        $assets = Asset::select('assets.*')->where('asset_status_id', $legalHoldStatusId)->NotBackedUp();

        if ($assets) {
            $assets = $assets->whereIn('asset_type_id', $this->assetTypeIds);
        }

        return $assets;
    }

    /**
     * Retrieves the user discrepancy by querying multiple discovery tools.
     *
     * @return null|\Illuminate\Database\Eloquent\Builder
     */
    public function getUserDiscrepancy()
    {
        $discoveryTools = config('asset-health.discovery_tools');
        $assets = null;

        foreach ($discoveryTools as $discoveryTool) {
            $assetsQuery = $this->getUserDiscrepancyByMdm($discoveryTool['user_discrepancy_statuses'], $discoveryTool['table_name']);

            if ($assets === null) {
                $assets = $assetsQuery;
            } else {
                $assets = $assets->union($assetsQuery);
            }
        }

        return $assets;
    }

    /**
     * Retrieves the user discrepancy based on the provided statuses and table name.
     *
     * @param array $statuses An array of asset statuses.
     * @param string $tableName The name of the table to join.
     *
     * @return Illuminate\Database\Query\Builder The query builder object.
     */
    private function getUserDiscrepancyByMdm($statuses, $tableName)
    {
        $statusIds = AssetStatus::whereIn('slug', $statuses)->pluck('id')->toArray();

        return Asset::select('assets.*')
            ->join($tableName, function ($join) use ($statusIds, $tableName) {
                $join->on('assets.id', '=', "{$tableName}.asset_id")
                    ->where('assets.user_id', '<>', "{$tableName}.user_id")
                    ->whereIn('asset_status_id', $statusIds)
                    ->whereIn('asset_type_id', $this->assetTypeIds);
            });
    }

    /**
     * Retrieves the list of assigned devices that are not active.
     *
     * @return \Illuminate\Database\Eloquent\Collection|null The list of assigned devices that are not active.
     */
    public function getAssignedDeviceNotActive()
    {
        $discoveryTools = config('asset-health.discovery_tools');
        $assets = null;

        $statusIds = AssetStatus::assignedDevicesNotActiveStatuses()->pluck('id')->toArray();

        foreach ($discoveryTools as $discoveryTool) {
            $assetsQuery = $this->getAssignedDevicesNotActiveByMdm(
                $statusIds,
                $discoveryTool['table_name'],
                $discoveryTool['last_seen_column']
            );

            $assets = $assets === null ? $assetsQuery : $assets->union($assetsQuery);
        }

        return $assets;
    }

    /**
     * Retrieves the assigned devices that are not active by MDM.
     *
     * @param array $statusIds The array of device statuses to filter by.
     * @param string $tableName The name of the table to join with.
     * @param string $lastSeenColumn The name of the column representing the last seen time.
     *
     * @return Illuminate\Database\Query\Builder The query builder instance.
     */
    private function getAssignedDevicesNotActiveByMdm($statusIds, $tableName, $lastSeenColumn)
    {
        return Asset::select('assets.*')
            ->join($tableName, function ($join) use ($statusIds, $tableName, $lastSeenColumn) {
                $join->on('assets.id', '=', "{$tableName}.asset_id")
                    ->where("{$tableName}.{$lastSeenColumn}", '<', now()->subDays(30))
                    ->whereIn('asset_status_id', $statusIds)
                    ->whereIn('asset_type_id', $this->assetTypeIds);
            });
    }

    /**
     * Retrieves the assets that are not managed in the MDM system.
     *
     * @return null|Illuminate\Database\Eloquent\Collection The assets not managed in MDM.
     */
    public function getAssetsNotInMdm()
    {
        $assets = null;

        $repositories = [
            'intune' => $this->intuneRepository,
            'airwatch' => $this->workspaceRepository,
            'chromebook' => $this->chromebookRepository,
            'mobile_iron' => $this->mobileIronRepository,
            'jamf' => $this->jamfRepository,
            'kandji' => $this->kandjiRepository
        ];

        foreach ($repositories as $mdmName => $repository) {
            $notManagedFunction = config('asset-health.discovery_tools')[$mdmName]['not_in_mdm_method'];
            $notManaged = $repository->{$notManagedFunction}();

            if ($notManaged) {
                $notManaged->whereIn('asset_type_id', $this->assetTypeIds);
            }

            if ($assets === null) {
                $assets = $notManaged;
            } else {
                $assets = $assets->union($notManaged);
            }
        }

        return $assets;
    }

    /**
     * Retrieves the assets assigned to inactive users.
     *
     * @return Illuminate\Database\Query\Builder The assets assigned to inactive users.
     */
    public function getAssignedToInactiveUser()
    {
        return Asset::hasStatus('assigned')
            ->inActiveUsers()
            ->when($this->assetTypeIds, function ($query, $assetTypeIds) {
                return $query->whereIn('asset_type_id', $assetTypeIds);
            });
    }

    /**
     * Retrieves all assets that are damaged for longer than thirty days.
     *
     * @return Illuminate\Database\Query\Builder Returns a collection of assets.
     */
    public function getDamagedLongerThanThirtyDays()
    {
        return Asset::whereIn('asset_type_id', $this->assetTypeIds)
            ->damagedOverThirtyDays();
    }

    /**
     * Retrieves assets that are over three years old.
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function getAssetAgeOverThreeYears()
    {
        return Asset::whereDate('created_at', '<', now()->subYears(3)->toDateString())
            ->whereIn('asset_type_id', $this->assetTypeIds);
    }

    /**
     * Retrieves the overdue loaner assets.
     *
     * @return \Illuminate\Database\Eloquent\Builder The query builder for retrieving overdue loaner assets.
     */
    public function getOverdueLoaner()
    {
        return Asset::hasStatus('loan_or_test')
            ->where('loaner_return_date', '<', now()->toDateString())
            ->whereIn('asset_type_id', $this->assetTypeIds);
    }

    /**
     * Retrieves the overdue retain hold assets.
     *
     * This function queries the database to retrieve the assets that have a status of 'retain_hold' and a loaner retention date that is earlier than the current date. The assets returned are filtered based on the asset type IDs passed to the function.
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function getOverdueRetainHoldAssets()
    {
        return Asset::hasStatus('retain_hold')
            ->whereDate('loaner_retention_date', '<', now()->toDateString())
            ->whereIn('asset_type_id', $this->assetTypeIds);
    }

    /**
     * Retrieves assets that are installed in inactive locations.
     *
     * @return \Illuminate\Database\Query\Builder
     */
    public function getInstalledInInactiveLocation()
    {
        return Asset::hasStatus('installed')
            ->inActiveLocations()
            ->whereIn('asset_type_id', $this->assetTypeIds);
    }

    /**
     * Retrieves the missing purchase orders.
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function getMissingPurchaseOrders()
    {
        return Asset::whereNull('po_id')
            ->whereIn('asset_type_id', $this->assetTypeIds);
    }

    /**
     * Retrieves the missing vendor for the assets.
     *
     * @return Builder The query builder instance.
     */
    public function getMissingVendor()
    {
        return Asset::select('assets.*')
            ->join('purchase_orders', function ($join) {
                $join->on('assets.po_id', '=', 'purchase_orders.id')
                    ->whereNull('assets.vendor_id');
            })
            ->whereIn('asset_type_id', $this->assetTypeIds);
    }

    /**
     * Retrieves the missing purchase price for assets.
     *
     * @return \Illuminate\Database\Query\Builder
     */
    public function getMissingPurchasePrice()
    {
        return Asset::select('assets.*')
            ->Join('technical_specs', function ($join) {
                $join->on('assets.technical_spec_id', '=', 'technical_specs.id')
                    ->whereNull('original_value');
            })->whereIn('asset_type_id', $this->assetTypeIds);
    }

    /**
     * Retrieves the assets that have expired warranties.
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function getExpiredWarrantyAssets()
    {
        return Asset::whereDate('warranty_end_date', '<', now()->toDateString())
            ->whereIn('asset_type_id', $this->assetTypeIds);
    }

    /**
     * Retrieves the assets that were last seen with a specific status.
     *
     * @param int $statusId The ID of the status.
     *
     * @return \Illuminate\Database\Query\Builder The assets that were last seen with the specified status.
     */
    private function getLastseenByStatus($statusId)
    {
        $discoveryTools = config('asset-health.discovery_tools');
        $assets = null;

        foreach ($discoveryTools as $discoveryTool) {
            $assetsQuery = $this->getLastSeen($statusId, $discoveryTool['table_name'], $discoveryTool['last_seen_column']);

            if ($assets === null) {
                $assets = $assetsQuery;
            } else {
                $assets = $assets->union($assetsQuery);
            }
        }

        return $assets;
    }

    /**
     * Retrieves the last seen asset based on the provided status ID, table name, and last seen column.
     *
     * @param int $statusId The ID of the asset status.
     * @param string $tableName The name of the table to join with.
     * @param string $lastSeenColumn The name of the column representing the last seen timestamp.
     *
     * @return \Illuminate\Database\Eloquent\Builder The query builder instance.
     */
    private function getLastSeen($statusId, $tableName, $lastSeenColumn)
    {
        return Asset::select('assets.*')
            ->join($tableName, function ($join) use ($statusId, $tableName, $lastSeenColumn) {
                $join->on('assets.id', '=', "{$tableName}.asset_id")
                    ->whereNull("{$tableName}.{$lastSeenColumn}")
                    ->where('asset_status_id', $statusId)
                    ->whereIn('asset_type_id', $this->assetTypeIds);
            });
    }

    /**
     * Get devices on hand checked in.
     *
     * @return mixed
     */
    public function getDevicesOnHandCheckedIn()
    {
        $discoveryTools = config('asset-health.discovery_tools');
        $assets = null;

        $statusIds   = AssetStatus::onHandStatuses()->get()->pluck('id');

        foreach ($discoveryTools as $discoveryTool) {
            $assetsQuery = $this->getDevicesOnHandCheckedInByMdm(
                $discoveryTool['table_name'],
                $discoveryTool['last_seen_column'],
                $statusIds
            );

            $assets = $assets === null ? $assetsQuery : $assets->union($assetsQuery);
        }

        return $assets;
    }

    /**
     * Gets devices on hand checked in by MDM.
     *
     * @param string $tableName the name of the table
     * @param string $lastSeenColumn the name of the last seen column
     * @param array $statusIds an array of status ids
     *
     * @return Illuminate\Database\Eloquent\Builder
     */
    private function getDevicesOnHandCheckedInByMdm($tableName, $lastSeenColumn, $statusIds)
    {
        return Asset::select('assets.*')
            ->join($tableName, function ($join) use ($tableName) {
                $join->on('assets.id', '=', "{$tableName}.asset_id");
            })->where("{$tableName}.{$lastSeenColumn}", '>', DB::raw('assets.last_status_update'))
            ->whereIn('assets.asset_status_id', $statusIds)
            ->whereIn('assets.asset_type_id', $this->assetTypeIds);
    }

    /**
     * Retrieves managed deployed assets that have not checked in, using the configured discovery tools.
     */
    public function getManagedDeployedNotCheckedIn()
    {
        $discoveryTools = config('asset-health.discovery_tools');
        $assets = null;

        $statusIds = AssetStatus::managedDeployedNotCheckedInStatuses()->get()->pluck('id');

        foreach ($discoveryTools as $discoveryTool) {
            $assetsQuery = $this->getManagedDeployedNotCheckedInByMdm(
                $discoveryTool['table_name'],
                $discoveryTool['last_seen_column'],
                $statusIds
            );

            $assets = $assets === null ? $assetsQuery : $assets->union($assetsQuery);
        }

        return $assets;
    }

    /**
     * Get managed deployed assets not checked in by MDM.
     *
     * @param string $tableName
     * @param string $lastSeenColumn
     * @param array $statusIds
     *
     * @return \Illuminate\Database\Eloquent\Collection
     */
    private function getManagedDeployedNotCheckedInByMdm($tableName, $lastSeenColumn, $statusIds)
    {
        return Asset::select('assets.*')
            ->join("{$tableName}", 'assets.id', '=', "{$tableName}.asset_id")
            ->where("{$tableName}.{$lastSeenColumn}", '<', \Carbon\Carbon::now()->subDays(30))
            ->whereIn('assets.asset_status_id', $statusIds)
            ->whereIn('assets.asset_type_id', $this->assetTypeIds)
            ->distinct();
    }
}
