<?php

namespace App\Services\SoftwareLicense\ImportedAssets;

use App\Http\Traits\SoftwareLicense\SoftwareAssetTrait;
use Illuminate\Support\Facades\Cache;
use App\Repositories\ApiCredentialRepository;
use App\Repositories\SoftwareLicense\DropboxDataRepository;
use App\Services\Integrations\SoftwareLicense\DropboxIntegration;
use Exception;
use Illuminate\Support\Facades\Log;

class DropboxService implements ImportedAssetsServiceInterface
{
    
    use SoftwareAssetTrait;
    
    private $slug;
    private $name;
    private $requiredScopes;
    protected $redirectURL;
    private $apiCredentialRepo;
    private $softwareAssetDataRepo;
    private $integration;
    protected $licenseService;

    /**
     * Constructor for DropboxService
     * 
     * @param ApiCredentialRepository $apiCredentialRepo
     * @param DropboxIntegration $integration
     * @param DropboxDataRepository $softwareAssetDataRepo
     */
    public function __construct(ApiCredentialRepository $apiCredentialRepo, DropboxIntegration $integration, DropboxDataRepository $softwareAssetDataRepo)
    {
        $this->slug = 'dropbox';
        $this->name = 'Dropbox';
        $this->requiredScopes = config('software-license.imported_softwares_data.dropbox.scopes');
        $this->redirectURL = url(config('software-license.imported_softwares_data.dropbox.callback_url'));
        $this->apiCredentialRepo = $apiCredentialRepo;
        $this->integration = $integration;
        $this->softwareAssetDataRepo = $softwareAssetDataRepo;
    }
    
    /**
     * get license api credentials
     * 
     * @return mixed
     */
    public function getCredentials() {
        $data = Cache::get('dropBoxCredentials');

        if ($data === null) {
            $data =  $this->apiCredentialRepo->getCredentials($this->slug); 
            Cache::put('dropBoxCredentials', $data);
        }

        return $data;
    }

    /**
     * check status of the license api connection
     * 
     * @param object $credentialsData
     * 
     * @return bool
     */
    public function checkConnection($credentialsData)
    {
        $accessToken = $this->getAccessToken($credentialsData);
        return $this->integration->checkConnection($accessToken);
    }

    /**
     * add credentials for license api
     * 
     * @param array $credentialsData
     * 
     * @return bool
     */
    public function addCredentials($credentialsData)
    {
        try {
            $this->apiCredentialRepo->addCredentials($credentialsData, $this->slug, $this->name);

            return true;
        } catch (Exception $e) {
            Log::channel('daily')->error($this->name . ' credential create : ' . $e->getMessage());

            return false;
        }
    }

    /**
     * update credentials for license api
     * 
     * @param array $data
     * 
     * @return bool
     */
    public function updateCredentials($data)
    {
        try {
            $credentialsData = $this->getCredentials();

            if (!$credentialsData) {
                return false;
            }
            $updateData = [
                'user_name' => $data['app_name'] ? $data['app_name'] : $credentialsData->user_name,
                'key'       => $data['app_key'] ? $data['app_key'] : $credentialsData->key,
                'password'  => $data['app_secret'] ? $data['app_secret'] : $credentialsData->password              
            ];

            if ($data['app_key'] != '' || $data['app_secret'] != '') {
                $updateData['data'] = '';
            }
            $this->apiCredentialRepo->updateCredentials($updateData, $this->slug);
            Cache::forget('dropBoxCredentials');

            return true;
        } catch (Exception $e) {
            Log::channel('daily')->error($this->name . ' credential create : ' . $e->getMessage());

            return false;
        }
    }

    /**
     * set connection for license api
     * 
     * @param array $data
     * 
     * @return bool
     */
    public function setConnection()
    {
        $credentialsData = $this->getCredentials();

        if (!$credentialsData) {
            return false;
        }
        $tokenData = $credentialsData->data ? json_decode($credentialsData->data, true) : [];
        $refreshedAccessToken = $this->integration->refreshToken($tokenData, $credentialsData->key, $credentialsData->password);

        if ($refreshedAccessToken) {
            $tokenData['access_token'] = $refreshedAccessToken;
            $dataToUpdate = ['data' => json_encode($tokenData)];
            $this->apiCredentialRepo->updateCredentials($dataToUpdate, $this->slug);
            Cache::forget('dropBoxCredentials');
        }
        $accessToken = $tokenData['access_token'] ?? '';

        return $this->integration->checkConnection($accessToken);
    }

    /**
     * It returns the URL for authenticate the license api app
     * 
     * @return string authentication URL is being returned.
     */
    public function getAuthenticationURL()
    {
        return config('services.dropbox.authorize_url').'?client_id='. $this->getCredentials()->key .'&response_type=code&token_access_type=offline&redirect_uri='. $this->redirectURL;
    }

    /**
     * Generate access token for the license api connection
     * 
     * @param string $code
     * @param object $credentialsData
     * 
     * @return mixed
     */
    public function generateAccessToken($code, $credentialsData)
    {
        return $this->integration->generateAccessToken($code, $this->redirectURL, $credentialsData->key,  $credentialsData->password);
    }

    /**
     * It checks if the user has the required scopes to access the resource
     * 
     * @param authorizationResponse The response from the authorization server.
     * 
     * @return array
     */
    public function checkRequiredScopes($authorizationResponse) 
    {
        if ($authorizationResponse['scope']) {
            $userScopes = explode(' ', $authorizationResponse['scope']);
            $error ='';
            $nonExistigScopes = [];

            foreach ($this->requiredScopes as $scope) {
                
                if (!in_array($scope,$userScopes)) {
                    $nonExistigScopes[] = $scope;
                    $error = 1;
                }
            }

            return [
                'error' => $error,
                'nonExistigScopes' => $nonExistigScopes
            ];            
        }
    }

    /**
     * fetch access token from license api credentials data
     * 
     * @param mixed $credentialsData
     * 
     * @return mixed
    */
    public function getAccessToken($credentialsData)
    {
        if ($credentialsData) {
            $tokenData = $credentialsData->data ? json_decode($credentialsData->data) : [];

            return isset($tokenData->access_token) ? $tokenData->access_token : '';
        }

        return false;
    }

    /**
     * It updates the access token to license api credentials
     * 
     * @param mixed $authorizationResponse
     * 
     * @return bool
    */
    public function updateAccessToken($authorizationResponse)
    {
        try {
            
            if (isset($authorizationResponse['access_token']) && $authorizationResponse['access_token'] !='') {
                $dataToUpdate = ['data' => json_encode($authorizationResponse)];
                $this->apiCredentialRepo->updateCredentials($dataToUpdate, $this->slug);
                Cache::forget('dropBoxCredentials');

                return true;
            }
            
            return false;
        } catch (Exception $e) {
            Log::channel('daily')->error($this->name . ' update access token : ' . $e->getMessage());

            return false;
        }
    }

    /**
     * This function changes the integration status of license API and enables/disables the license software asset accordingly
     * 
     * @return bool
     */
    public function integrationStatusChange()
    {
        $integrationStatus = request('integration_status') ?? NULL;

        try {
            $dataToUpdate = ['integration_status' => $integrationStatus];
            $this->apiCredentialRepo->updateCredentials($dataToUpdate, $this->slug);
            Cache::forget('dropBoxCredentials');
            
            $licensedata = $this->getLicenseData();

            if ($licensedata) {
                $data = ['status' => $integrationStatus];
                $statusUpdate = $this->softwareAssetDataRepo->updateLicenseData($licensedata->id, $data);
           
                if ($statusUpdate && ($licensedata->status != $integrationStatus)) {
                    $data['id'] = $licensedata->id;
                    $updateLicenseHistoryData = $this->setImportedAssetHistoryForUpdateWithIntegration($licensedata, $data);
                    $this->softwareAssetDataRepo->addLicenseHistory($updateLicenseHistoryData);
                }
            } else {
                $softwareAssetData = $this->formatAssetDataForCreateWithIntegration($this->name);
                $assetCreated = $this->softwareAssetDataRepo->createLicenseData($softwareAssetData);
                $addLicenseHistoryData = $this->setImportedAssetHistoryForCreateWithIntegration($assetCreated);
                $this->softwareAssetDataRepo->addLicenseHistory($addLicenseHistoryData);
            }

            return true;
        } catch (Exception $e) {
            Log::channel('daily')->error($this->name . ' integration status update error : ' . $e->getMessage());
            
            return false;
        }   
    }

    /**
     * This function fetch license software asset data
     * 
     * @return mixed
     */
    public function getLicenseData()
    {
        return $this->softwareAssetDataRepo->getLicenseData();
    }

    /**
     * This function updates the number of licenses purchased for a license  asset
     * 
     * @param object $assetDetails
     * 
     * @return bool 
     */
    public function updateLicencePurchasedCount($credentialsData, $assetDetail)
    {
        try {
            $accessToken = $this->getAccessToken($credentialsData);
            $licensePurchasedCount = $this->integration->updateLicencePurchasedCount($accessToken);
            $data = ['licenses_purchased' => $licensePurchasedCount];
            $this->softwareAssetDataRepo->updateLicenseData($assetDetail->id, $data);

            return true;
        } catch (Exception $e) {
            Log::channel('daily')->error($this->name . ' update license purchased count update error : ' . $e->getMessage());
            
            return false;
        } 
    }

    /**
     * This function creates license members by fetching a list of members from API and add to license  members table.
     * 
     * @param mixed $credentialsData
     * 
     */
    public function createMembers($credentialsData)
    {
        $cursor = '';
        $hasMore = true;

        try {
            $accessToken = $this->getAccessToken($credentialsData);
            $this->softwareAssetDataRepo->clearExistingDataBeforeFetching();
            $memberDataForCreate = [];

            while ($hasMore == true) {
                $softwareAssetMemberList = $this->integration->getSoftwareAssetMemberList($accessToken, $cursor, $hasMore);
                $cursor  = $softwareAssetMemberList['cursor'] ?? '';
                $hasMore = $softwareAssetMemberList['has_more'] ?? false;

                if (isset($softwareAssetMemberList['members'])) {
                    
                    foreach ($softwareAssetMemberList['members'] as $member) {
                        $memberData = $this->getMemberData($member);
                        $memberDataForCreate[] = $memberData;
                    }
                } 
            }

            if (!empty($memberDataForCreate)) {
                $this->softwareAssetDataRepo->createMembers($memberDataForCreate);
            }

            return true;

        } catch(Exception $e) {
            Log::channel('daily')->info($this->name . " team members fetch error ---- " . $e->getMessage());

            return false;
        }
    }

    /** 
     * It parses the response from the license  API and returns an array of data to create or update software asset members. 
     * @param member The member object returned by the license API
     * 
     * @return array
    */
    public function getMemberData($member)
    {
       $memberProfile  = $member['profile'];
       $user = $this->getUserForLicenseMemeber('email', $memberProfile['email']);
       $data['user_id']             = $user ? optional($user)->id :null ;
       $data['team_member_id']      = $memberProfile['team_member_id'] ?? '';
       $data['account_id']          = $memberProfile['account_id'] ?? '';
       $data['email']               = $memberProfile['email'] ?? '';
       $data['email_verified']      = $memberProfile['email_verified'] ?? false;
       $data['status']              = $memberProfile['status'] ? $memberProfile['status']['.tag'] : '';
       $data['name']                = $memberProfile['name'] ? ($memberProfile['name']['given_name'].' '.$memberProfile['name']['surname']) : '';
       $data['membership_type']     = $memberProfile['membership_type'] ? $memberProfile['membership_type']['.tag'] : '';
       $data['joined_on']           = $memberProfile['joined_on'] ?? Null;
       $data['groups']              = $memberProfile['groups'] ? json_encode($memberProfile['groups']) : '';
       $data['member_folder_id']    = $memberProfile['team_member_id'] ?? '';
       $data['role']                = $member['role'] ? $member['role']['.tag'] : '';
       
       return $data;
    }

    /**
     * This function syncs the list of software asset members with license members and updates the asset owner data.
     * 
     * @param object assetDetails
     * 
     * @return bool
     */
    public function softwareAssetMemberListSync($assetDetails)
    {
        try {
            $softwareAssetMembers = $this->softwareAssetDataRepo->getMemberData();
            $usersToBeDeleted = $this->softwareAssetDataRepo->getOldUsersToBeDeleted($assetDetails->id);
            $this->softwareAssetDataRepo->deleteOldUsersToBeDeleted($assetDetails->id, $usersToBeDeleted);
            $softwareUserhistorydata = $this->setSoftwareUserHistoryData('user_removed', $assetDetails, $usersToBeDeleted);
            $this->softwareAssetDataRepo->addLicenseHistory($softwareUserhistorydata);
            
            if (!$softwareAssetMembers) {
                return true;
            }

            $newlyAddedUser = [];

            foreach($softwareAssetMembers as $softwareAssetMemberData) {
                $licenceUserData = $this->getSoftwareAssetUserData($assetDetails, $softwareAssetMemberData);
                $conditions = [
                    'software_license_id' => $assetDetails->id,
                    'user_id'   => $softwareAssetMemberData->user_id
                ];
                $update = $this->softwareAssetDataRepo->updateLicenseUsers($conditions, $licenceUserData);
                

                if ($update->wasRecentlyCreated) {
                    $newlyAddedUser[] = $softwareAssetMemberData->user_id;
                } 
            }

            if (!empty($newlyAddedUser)) {
                $softwareUserhistorydata = $this->setSoftwareUserHistorydata('user_added', $assetDetails, $newlyAddedUser);
                $this->softwareAssetDataRepo->addLicenseHistory($softwareUserhistorydata);
            }

            return true;
        } catch(Exception $e) {
            Log::channel('daily')->info($this->name . " member list sync ---- " . $e->getMessage());

            return false;
        }
       
    }

    /**
     * Updates the owner of a software asset
     * 
     * @param mixed $assetDetails
     * 
     * @return bool
     */
    public function updateSoftwareAssetOwner($assetDetails)
    {
        try {
            $owner = $this->softwareAssetDataRepo->getSoftwareLicenseOwner('team_admin');
        
            if ($owner) {
                $data = ['owner_id' => $owner->user_id];
                $this->softwareAssetDataRepo->updateLicenseData($assetDetails->id, $data);
            }

            return true;
        } catch(Exception $e) {
            Log::channel('daily')->info("Update " . $this->name . " owner ---- " . $e->getMessage());

            return false;
        }
    }

    /**
     * Updates the licenses used count of a software asset with the count of all active users in asset members
     * 
     * @param object $assetDetails
     * 
     * @return bool
     */
    public function updateLicensesUsedCount($assetDetails)
    {
        try {
            $count = $this->softwareAssetDataRepo->getSoftwareLicenseActiveUserCount();
        
            if ($count > 0) {
                $data = ['licenses_used' =>  $count];
                $this->softwareAssetDataRepo->updateLicenseData($assetDetails->id, $data);
            }

            return true;
        } catch(Exception $e) {
            Log::channel('daily')->info("Update " . $this->name . " licenses used count ---- " . $e->getMessage());

            return false;
        }
    }

}