<?php

namespace App\Services\SoftwareLicense\ImportedAssets;

use App\Http\Traits\SoftwareLicense\SoftwareAssetTrait;
use App\Repositories\ApiCredentialRepository;
use App\Services\Integrations\SoftwareLicense\SlackIntegration;
use App\Repositories\SoftwareLicense\SlackDataRepository;
use Exception;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Carbon\Carbon;

class SlackService implements ImportedAssetsServiceInterface
{

    use SoftwareAssetTrait;

    private $slug;
    private $name;
    private $requiredScopes;
    protected $redirectURL;
    protected $state;
    private $apiCredentialRepo;
    private $integration;
    private $softwareAssetDataRepo;

    /**
     * Constructor for SlackService
     * 
     * @param ApiCredentialRepository $apiCredentialRepo
     * @param SlackIntegration $integration
     * @param SlackDataRepository $softwareAssetDataRepo
     */
    public function __construct(ApiCredentialRepository $apiCredentialRepo, SlackIntegration $integration, SlackDataRepository $softwareAssetDataRepo)
    {
        $this->slug = 'slack';
        $this->name = 'Slack';
        $this->requiredScopes = config('software-license.imported_softwares_data.slack.scopes');
        $this->redirectURL = url(config('software-license.imported_softwares_data.slack.callback_url'));
        $this->state = 'STATE';
        $this->apiCredentialRepo = $apiCredentialRepo;
        $this->integration = $integration;
        $this->softwareAssetDataRepo = $softwareAssetDataRepo;
    }

    /**
     * get license api credentials
     * 
     * @return mixed
     */
    public function getCredentials() {
        $data = Cache::get('slackCredentials');

        if ($data === null) {
            $data =  $this->apiCredentialRepo->getCredentials($this->slug); 
            Cache::put('slackCredentials', $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('slackCredentials');

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

            return false;
        }
    }

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

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


        if (isset($refreshAccessTokenResponse['ok']) && $refreshAccessTokenResponse['ok'] === true) {
            $dataToUpdate = ['data' => json_encode($refreshAccessTokenResponse)];
            $this->apiCredentialRepo->updateCredentials($dataToUpdate, $this->slug);
            Cache::forget('slackCredentials');
        }
        $accessToken = $refreshAccessTokenResponse['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.slack.authorize_url').'?client_id='. $this->getCredentials()->key . '&scope=' . $this->requiredScopes . '&redirect_uri=' . $this->redirectURL . '&state=' . $this->state;
    }

    /**
     * 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 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('slackCredentials');

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

            return false;
        }
    }

    /**
     * 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;
    }

    /**
     * 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('slackCredentials');
            
            $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();
    }

    public function updateLicencePurchasedCount($credentialsData, $assetDetail)
    {
        return true;
    }

    /**
     * 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)
    {
        $nextPageToken = '';

        try {
            $accessToken = $this->getAccessToken($credentialsData);
            $this->softwareAssetDataRepo->clearExistingDataBeforeFetching();
            $memberDataForCreate = [];
            
            do {
                $softwareAssetMemberList = $this->integration->getSoftwareAssetMemberList($accessToken, $nextPageToken);
                $nextPageToken  = $softwareAssetMemberList['ok'] ? $softwareAssetMemberList['response_metadata']['next_cursor'] : '';

                if (isset($softwareAssetMemberList['members'])) {
                    
                    foreach ($softwareAssetMemberList['members'] as $member) {
                        
                        if ($member['deleted'] === false && $member['name'] != 'slackbot' && $member['is_bot'] === false)
                        {
                            $memberData = $this->getMemberData($member);
                            $memberDataForCreate[] = $memberData;
                        }
                    }
                }
                
            } while ($nextPageToken);

            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)
    {
        $memberEmail = $member['profile']['email'] ?? '';
        $user = $this->getUserForLicenseMemeber('email', $memberEmail);
        $data['user_id']             = $user ? optional($user)->id :null ;
        $data['slack_user_id']       = $member['id'];
        $data['email']               = $memberEmail;
        $data['email_verified']      = $member['is_email_confirmed'];
        $data['status']              = 'Active';
        $data['name']                = !empty($member['profile']['display_name']) ? $member['profile']['display_name'] : $member['name'];
        $data['joined_on']           = NULL;
        $role = 'Member';

        switch (true) {
            case $member['is_primary_owner']:
                $role = 'Primary Owner';
                break;
            case $member['is_owner']:
                $role = 'Owner';
                break;
            case $member['is_admin']:
                $role = 'Admin';
                break;
        }
        $data['role'] = $role;
        $data['created_at'] = Carbon::now(); 
        $data['updated_at'] = Carbon::now();
        
        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('Primary Owner');
        
            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)
    {
        return true;
    }

    
}