<?php

namespace Cobblestone\CcLogin\Service;

use Cobblestone\CcLogin\Entity\Position;
use Exception;
use InvalidArgumentException;
use Cobblestone\CcLogin\Entity\Asset;
use Cobblestone\CcLogin\Security\CCUser;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class AssetService extends AbstractCCService
{
    /**
     * @var LeaseService
     */
    private $leaseService;

    /**
     * @var TokenStorageInterface
     */
    protected $tokenStorage;

    /**
     * AssetService constructor.
     * @param LeaseService $leaseService
     * @param TokenStorageInterface $tokenStorage
     * @param string $apiUrl
     */
    public function __construct
    (
        LeaseService $leaseService,
        TokenStorageInterface $tokenStorage,
        string $apiUrl
    )
    {
        $this->leaseService = $leaseService;
        $this->tokenStorage = $tokenStorage;
        parent::__construct($apiUrl);
    }

    /**
     * @param string $token
     * @param int $applicationId
     * @return Asset[]|array
     * @throws Exception
     */
    public function getAssets(string $token, int $applicationId): array
    {
        $response = $this->callURMService( "/me/assets/$applicationId/role", $token);

        // If response is null, return empty array
        // Otherwise, convert API response to array of Asset objects
        $assets = $response === null ? [] : $this->handleAssetAPIResponse($response);

        return $assets;
    }

    /**
     * @param string $xApiKey
     * @return array
     */
    public function getAllAssets(string $xApiKey): array
    {
        $response = $this->callApi( "/assets?limit=2000", [
            "x-api-key: ${xApiKey}"
        ]);

        return $response;
    }

    /**
     * Returns all assets that the currently logged in user has access to.
     *
     * This can be found by calling the Assets API endpoint, but is actually also available cached on the CCUser, so we
     * can just fetch it from there.
     *
     * @return Asset[]|array|null
     */
    public function getUserAccessibleAssets()
    {
        /** @var CCUser $user */
        $user = $this->tokenStorage->getToken()->getUser();
        if ($user) {
            return $user->getAssets();
        }
        return [];

    }

    /**
     * @return array
     */
    public function getUserAccessibleAssetIds(): array {
        $assetIds = array_map(function ($asset) {
            if ($asset instanceof Asset) {
                return $asset->getId();
            } elseif (gettype($asset) === 'array' && isset($asset['id'])) {
                return $asset['id'];
            } else {
                $type = gettype($asset);
                throw new InvalidArgumentException("Type \"Asset\" or \"array\" expected. \"$type\" given.");
            }
        }, $this->getUserAccessibleAssets());

        return $assetIds;
    }

    /**
     * Checks the user accessible assets array for an asset corresponding to the provided asset id
     * If no asset is found for that id, returns null
     *
     * @param int|null $assetId
     * @return Asset|null
     */
    public function getAsset(?int $assetId): ?Asset
    {
        $assets = $this->getUserAccessibleAssets();

        foreach ($assets as $asset) {
            if ($asset->getId() === $assetId) {
                return $asset;
            }
        }

        return null;
    }

    /**
     * @param string $token
     * @return Position[]
     * @throws Exception
     */
    public function getAssetPositions(string $token): array
    {
        $response = $this->callURMService("/allowed-positions", $token);

        if (array_key_exists('result', $response)) {
            $positions = [];

            foreach ($response['result'] as $positionArr) {
                $position = new Position();

                $position->setId($positionArr['id']);
                $position->setTitle($positionArr['title']);

                $positions[] = $position;
            }

            return $positions;
        } else {
            throw new Exception("Unable to fetch positions");
        }
    }

    /**
     * Finds all Asset Positions that the user has access to
     *
     * @param string $token
     * @return array
     * @throws Exception
     */
    public function getUserAssetPositions(string $token): array
    {
        $response = $this->callURMService("/me/positions", $token);

        $userAssetPositions = [];

        foreach ($response as $assetId => $positions) {
            $positionIds = array_map(function (array $arrPosition) {
                return $arrPosition["id"];
            }, $positions);

            $userAssetPositions[] = [
                "assetId" => $assetId,
                "positionIds" => $positionIds,
            ];
        }

        return $userAssetPositions;
    }

    /**
     * Returns true if user has at least one Position set on the given Asset
     *
     * @param int $assetId
     * @return bool
     */
    public function userHasPositionOnAsset(int $assetId): bool
    {
        /** @var CCUser $user */
        $user = $this->tokenStorage->getToken()->getUser();

        foreach ($user->getUserAssetPositions() as $assetPosition) {
            if ($assetPosition["assetId"] === $assetId) {
                return true;
            }
        }

        return false;
    }

    /**
     * Returns true if user has the given Position set on the given Asset
     *
     * @param int $assetId
     * @param int $positionId
     * @return bool
     */
    public function userHasSpecificPositionOnAsset(int $assetId, int $positionId): bool
    {
        /** @var CCUser $user */
        $user = $this->tokenStorage->getToken()->getUser();

        foreach ($user->getUserAssetPositions() as $assetPosition) {
            if ($assetPosition["assetId"] === $assetId && in_array($positionId, $assetPosition["positionIds"])) {
                return true;
            }
        }

        return false;
    }

    /**
     * Returns true if user has the given Position set on any Asset
     *
     * @param int $positionId
     * @return bool
     */
    public function userHasAssetOnPosition(int $positionId): bool
    {
        /** @var CCUser $user */
        $user = $this->tokenStorage->getToken()->getUser();

        foreach ($user->getUserAssetPositions() as $assetPosition) {
            if (in_array($positionId, $assetPosition["positionIds"])) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param int $positionId
     * @return Position|null
     */
    public function getAssetPosition(int $positionId): ?Position
    {
        /** @var CCUser $user */
        $user = $this->tokenStorage->getToken()->getUser();

        $positions = $user->getPositions();

        if (!empty($positions)) {
            foreach ($positions as $position) {
                if ($position->getId() === $positionId) {
                    return $position;
                }
            }
        }

        return null;
    }

    /**
     * @param string $positionName
     * @return Position|null
     */
    public function getAssetPositionByName(string $positionName): ?Position
    {
        /** @var CCUser $user */
        $user = $this->tokenStorage->getToken()->getUser();

        $positions = $user->getPositions();

        if (!empty($positions)) {
            foreach ($positions as $position) {
                if ($position->getTitle() === $positionName) {
                    return $position;
                }
            }
        }

        return null;
    }

    /**
     * Converts an array of asset arrays from the Role Management API to an array of Asset objects
     *
     * @param array $response
     * @return Asset[]|array
     */
    private function handleAssetAPIResponse(array $response): array
    {
        $assets = [];

        foreach ($response as $assetArr) {
            $id = intval($assetArr['id']);
            $title = $assetArr['title'];

            $leases = $this->leaseService->resultsToObjects($assetArr['leases'], $id);

            $externalIds = $assetArr['external_ids'];

            $cvr = $externalIds['cvr'];
            $unik = $externalIds['unik'];
            $microbiz = $externalIds['microbiz'];
            $timelog = $externalIds['timelog'];
            $pipedrive = $externalIds['pipedrive'];

            $asset = new Asset(
                $id,
                $title,
                $leases,
                $cvr,
                $unik,
                $microbiz,
                $timelog,
                $pipedrive
            );

            $assets[] = $asset;
        }

        return $assets;
    }
}
