<?php

namespace SecurionPay;

use SecurionPay\Connection\Connection;
use SecurionPay\Connection\CurlConnection;
use SecurionPay\Exception\SecurionPayException;
use SecurionPay\Util\ObjectSerializer;

class SecurionPayGateway
{
    const VERSION = '2.5.0';

    const DEFAULT_ENDPOINT = 'https://api.securionpay.com';

    const DEFAULT_UPLOADS_ENDPOINT = 'https://uploads.securionpay.com/';

    private $objectSerializer;

    /**
     * @var \SecurionPay\Connection\Connection
     */
    private $connection;

    private $privateKey;

    private $endpoint = self::DEFAULT_ENDPOINT;

    private $uploadsEndpoint = self::DEFAULT_UPLOADS_ENDPOINT;

    private $userAgent;

    public function __construct($privateKey = null, ?Connection $connection = null)
    {
        $this->objectSerializer = new ObjectSerializer;

        $this->privateKey = $privateKey;
        $this->connection = $connection ? $connection : new CurlConnection;
    }

    /**
     * @param  \SecurionPay\Request\ChargeRequest  $request
     * @return \SecurionPay\Response\Charge
     */
    public function createCharge($request)
    {
        return $this->post('/charges', $request, '\SecurionPay\Response\Charge');
    }

    /**
     * @param  \SecurionPay\Request\CaptureRequest  $request
     * @return \SecurionPay\Response\Charge
     */
    public function captureCharge($request)
    {
        return $this->post('/charges/{chargeId}/capture', $request, '\SecurionPay\Response\Charge');
    }

    /**
     * @param  string  $chargeId
     * @return \SecurionPay\Response\Charge
     */
    public function retrieveCharge($chargeId)
    {
        return $this->get("/charges/{$chargeId}", '\SecurionPay\Response\Charge');
    }

    /**
     * @param  \SecurionPay\Request\ChargeUpdateRequest  $request
     * @return \SecurionPay\Response\Charge
     */
    public function updateCharge($request)
    {
        return $this->post('/charges/{chargeId}', $request, '\SecurionPay\Response\Charge');
    }

    /**
     * @param  \SecurionPay\Request\RefundRequest  $request
     * @return \SecurionPay\Response\Refund
     *
     * @deprecated For backward compatibility only. Use "createRefund($request)".
     */
    public function refundCharge($request)
    {
        return $this->createRefund($request);
    }

    /**
     * @param  \SecurionPay\Request\ChargeListRequest  $request
     * @return \SecurionPay\Response\ListResponse
     */
    public function listCharges($request = null)
    {
        return $this->getList('/charges', $request, '\SecurionPay\Response\Charge');
    }

    /**
     * @param  \SecurionPay\Request\CustomerRequest  $request
     * @return \SecurionPay\Response\Customer
     */
    public function createCustomer($request)
    {
        return $this->post('/customers', $request, '\SecurionPay\Response\Customer');
    }

    /**
     * @param  string  $customerId
     * @return \SecurionPay\Response\Customer
     */
    public function retrieveCustomer($customerId)
    {
        return $this->get("/customers/{$customerId}", '\SecurionPay\Response\Customer');
    }

    /**
     * @param  \SecurionPay\Request\CustomerUpdateRequest  $request
     * @return \SecurionPay\Response\Customer
     */
    public function updateCustomer($request)
    {
        return $this->post('/customers/{customerId}', $request, '\SecurionPay\Response\Customer');
    }

    /**
     * @param  string  $customerId
     * @return \SecurionPay\Response\DeleteResponse
     */
    public function deleteCustomer($customerId)
    {
        return $this->delete("/customers/{$customerId}", null, '\SecurionPay\Response\DeleteResponse');
    }

    /**
     * @param  \SecurionPay\Request\CustomerListRequest  $request
     * @return \SecurionPay\Response\ListResponse
     */
    public function listCustomers($request = null)
    {
        return $this->getList('/customers', $request, '\SecurionPay\Response\Customer');
    }

    /**
     * @param  \SecurionPay\Request\CardRequest  $request
     * @return \SecurionPay\Response\Card
     */
    public function createCard($request)
    {
        return $this->post('/customers/{customerId}/cards', $request, '\SecurionPay\Response\Card');
    }

    /**
     * @param  string  $customerId
     * @param  string  $cardId
     * @return \SecurionPay\Response\Card
     */
    public function retrieveCard($customerId, $cardId)
    {
        return $this->get("/customers/{$customerId}/cards/{$cardId}", '\SecurionPay\Response\Card');
    }

    /**
     * @param  \SecurionPay\Request\CardUpdateRequest  $request
     * @return \SecurionPay\Response\Card
     */
    public function updateCard($request)
    {
        return $this->post('/customers/{customerId}/cards/{cardId}', $request, '\SecurionPay\Response\Card');
    }

    /**
     * @param  string  $customerId
     * @param  string  $cardId
     * @return \SecurionPay\Response\DeleteResponse
     */
    public function deleteCard($customerId, $cardId)
    {
        return $this->delete("/customers/{$customerId}/cards/{$cardId}", null, '\SecurionPay\Response\DeleteResponse');
    }

    /**
     * @param  \SecurionPay\Request\CardListRequest  $request
     * @return \SecurionPay\Response\ListResponse
     */
    public function listCards($request)
    {
        return $this->getList('/customers/{customerId}/cards', $request, '\SecurionPay\Response\Card');
    }

    /**
     * @param  string  $paymentMethodId
     * @return \SecurionPay\Response\PaymentMethod
     */
    public function retrievePaymentMethod($paymentMethodId)
    {
        return $this->get("/payment-methods/{$paymentMethodId}", '\SecurionPay\Response\PaymentMethod');
    }

    /**
     * @param  string  $paymentMethodId
     * @return \SecurionPay\Response\DeleteResponse
     */
    public function deletePaymentMethod($paymentMethodId)
    {
        return $this->delete("/payment-methods/{$paymentMethodId}", null, '\SecurionPay\Response\DeleteResponse');
    }

    /**
     * @param  \SecurionPay\Request\PaymentMethodListRequest  $request
     * @return \SecurionPay\Response\ListResponse
     */
    public function listPaymentMethods($request)
    {
        return $this->getList('/payment-methods', $request, '\SecurionPay\Response\PaymentMethod');
    }

    /**
     * @param  \SecurionPay\Request\SubscriptionRequest  $request
     * @return \SecurionPay\Response\Subscription
     */
    public function createSubscription($request)
    {
        return $this->post('/customers/{customerId}/subscriptions', $request, '\SecurionPay\Response\Subscription');
    }

    /**
     * @param  string  $customerId
     * @param  string  $subscriptionId
     * @return \SecurionPay\Response\Subscription
     */
    public function retrieveSubscription($customerId, $subscriptionId)
    {
        return $this->get("/customers/{$customerId}/subscriptions/{$subscriptionId}", '\SecurionPay\Response\Subscription');
    }

    /**
     * @param  \SecurionPay\Request\SubscriptionUpdateRequest  $request
     * @return \SecurionPay\Response\Subscription
     */
    public function updateSubscription($request)
    {
        return $this->post('/customers/{customerId}/subscriptions/{subscriptionId}', $request, '\SecurionPay\Response\Subscription');
    }

    /**
     * @param  \SecurionPay\Request\SubscriptionCancelRequest  $request
     * @return \SecurionPay\Response\Subscription
     */
    public function cancelSubscription($request)
    {
        return $this->delete('/customers/{customerId}/subscriptions/{subscriptionId}', $request, '\SecurionPay\Response\Subscription');
    }

    /**
     * @param  \SecurionPay\Request\SubscriptionListRequest  $request
     * @return \SecurionPay\Response\ListResponse
     */
    public function listSubscriptions($request)
    {
        return $this->getList('/customers/{customerId}/subscriptions', $request, '\SecurionPay\Response\Subscription');
    }

    /**
     * @param  \SecurionPay\Request\PlanRequest  $request
     * @return \SecurionPay\Response\Plan
     */
    public function createPlan($request)
    {
        return $this->post('/plans', $request, '\SecurionPay\Response\Plan');
    }

    /**
     * @param  string  $planId
     * @return \SecurionPay\Response\Plan
     */
    public function retrievePlan($planId)
    {
        return $this->get("/plans/{$planId}", '\SecurionPay\Response\Plan');
    }

    /**
     * @param  \SecurionPay\Request\PlanUpdateRequest  $request
     * @return \SecurionPay\Response\Plan
     */
    public function updatePlan($request)
    {
        return $this->post('/plans/{planId}', $request, '\SecurionPay\Response\Plan');
    }

    /**
     * @param  string  $planId
     * @return \SecurionPay\Response\DeleteResponse
     */
    public function deletePlan($planId)
    {
        return $this->delete("/plans/{$planId}", null, '\SecurionPay\Response\DeleteResponse');
    }

    /**
     * @param  \SecurionPay\Request\PlanListRequest  $request
     * @return \SecurionPay\Response\ListResponse
     */
    public function listPlans($request = null)
    {
        return $this->getList('/plans', $request, '\SecurionPay\Response\Plan');
    }

    /**
     * @param  string  $eventId
     * @return \SecurionPay\Response\Event
     */
    public function retrieveEvent($eventId)
    {
        return $this->get("/events/{$eventId}", '\SecurionPay\Response\Event');
    }

    /**
     * @param  \SecurionPay\Request\EventListRequest  $request
     * @return \SecurionPay\Response\ListResponse
     */
    public function listEvents($request = null)
    {
        return $this->getList('/events', $request, '\SecurionPay\Response\Event');
    }

    /**
     * @param  \SecurionPay\Request\TokenRequest  $request
     * @return \SecurionPay\Response\Token
     */
    public function createToken($request)
    {
        return $this->post('/tokens', $request, '\SecurionPay\Response\Token');
    }

    /**
     * @param  string  $tokenId
     * @return \SecurionPay\Response\Token
     */
    public function retrieveToken($tokenId)
    {
        return $this->get("/tokens/{$tokenId}", '\SecurionPay\Response\Token');
    }

    /**
     * @param  \SecurionPay\Request\BlacklistRuleRequest  $request
     * @return \SecurionPay\Response\BlacklistRule
     */
    public function createBlacklistRule($request)
    {
        return $this->post('/blacklist', $request, '\SecurionPay\Response\BlacklistRule');
    }

    /**
     * @param  string  $blacklistRuleId
     * @return \SecurionPay\Response\BlacklistRule
     */
    public function retrieveBlacklistRule($blacklistRuleId)
    {
        return $this->get("/blacklist/{$blacklistRuleId}", '\SecurionPay\Response\BlacklistRule');
    }

    /**
     * @param  string  $blacklistRuleId
     * @return \SecurionPay\Response\DeleteResponse
     */
    public function deleteBlacklistRule($blacklistRuleId)
    {
        return $this->delete("/blacklist/{$blacklistRuleId}", null, '\SecurionPay\Response\DeleteResponse');
    }

    /**
     * @param  \SecurionPay\Request\BlacklistRuleListRequest  $request
     * @return \SecurionPay\Response\ListResponse
     */
    public function listBlacklistRules($request = null)
    {
        return $this->getList('/blacklist', $request, '\SecurionPay\Response\BlacklistRule');
    }

    /**
     * @param  \SecurionPay\Request\CrossSaleOfferRequest  $request
     * @return \SecurionPay\Response\CrossSaleOffer
     */
    public function createCrossSaleOffer($request)
    {
        return $this->post('/cross-sale-offers', $request, '\SecurionPay\Response\CrossSaleOffer');
    }

    /**
     * @param  string  $blacklistRuleId
     * @return \SecurionPay\Response\CrossSaleOffer
     */
    public function retrieveCrossSaleOffer($crossSaleOfferId)
    {
        return $this->get("/cross-sale-offers/{$crossSaleOfferId}", '\SecurionPay\Response\CrossSaleOffer');
    }

    /**
     * @param  \SecurionPay\Request\CrossSaleOfferUpdateRequest  $request
     * @return \SecurionPay\Response\CrossSaleOffer
     */
    public function updateCrossSaleOffer($request)
    {
        return $this->post('/cross-sale-offers/{crossSaleOfferId}', $request, '\SecurionPay\Response\CrossSaleOffer');
    }

    /**
     * @param  string  $crossSaleOfferId
     * @return \SecurionPay\Response\DeleteResponse
     */
    public function deleteCrossSaleOffer($crossSaleOfferId)
    {
        return $this->delete("/cross-sale-offers/{$crossSaleOfferId}", null, '\SecurionPay\Response\DeleteResponse');
    }

    /**
     * @param  \SecurionPay\Request\CrossSaleOfferListRequest  $request
     * @return \SecurionPay\Response\ListResponse
     */
    public function listCrossSaleOffers($request)
    {
        return $this->getList('/cross-sale-offers', $request, '\SecurionPay\Response\CrossSaleOffer');
    }

    /**
     * @param  \SecurionPay\Request\CreditRequest  $request
     * @return \SecurionPay\Response\Credit
     */
    public function createCredit($request)
    {
        return $this->post('/credits', $request, '\SecurionPay\Response\Credit');
    }

    /**
     * @param string creditId
     * @return \SecurionPay\Response\Credit
     */
    public function retrieveCredit($creditId)
    {
        return $this->get("/credits/{$creditId}", '\SecurionPay\Response\Credit');
    }

    /**
     * @param  \SecurionPay\Request\CreditUpdateRequest  $request
     * @return \SecurionPay\Response\Credit
     */
    public function updateCredit($request)
    {
        return $this->post('/credits/{creditId}', $request, '\SecurionPay\Response\Credit');
    }

    /**
     * @param  \SecurionPay\Request\CreditListRequest  $request
     * @return \SecurionPay\Response\ListResponse
     */
    public function listCredits($request = null)
    {
        return $this->getList('/credits', $request, '\SecurionPay\Response\Credit');
    }

    /**
     * @param  string  $file
     * @param  string  $purpose
     * @return \SecurionPay\Response\FileUpload
     */
    public function createFileUpload($file, $purpose)
    {
        $files = ['file' => $file];
        $form = ['purpose' => $purpose];

        return $this->multipart('/files', $files, $form, '\SecurionPay\Response\FileUpload');
    }

    /**
     * @param  string  $fileUploadId
     * @return \SecurionPay\Response\FileUpload
     */
    public function retrieveFileUpload($fileUploadId)
    {
        return $this->getFromEndpoint($this->uploadsEndpoint, "/files/{$fileUploadId}", '\SecurionPay\Response\FileUpload');
    }

    /**
     * @param  \SecurionPay\Request\FileUploadListRequest  $request
     * @return \SecurionPay\Response\ListResponse
     */
    public function listFileUploads($request = null)
    {
        return $this->listFromEndpoint($this->uploadsEndpoint, '/files', $request, '\SecurionPay\Response\FileUpload');
    }

    /**
     * @param  string  $disputeId
     * @return \SecurionPay\Response\Dispute
     */
    public function retrieveDispute($disputeId)
    {
        return $this->get("/disputes/{$disputeId}", '\SecurionPay\Response\Dispute');
    }

    /**
     * @param  \SecurionPay\Request\DisputeUpdateRequest  $request
     * @return \SecurionPay\Response\Dispute
     */
    public function updateDispute($request)
    {
        return $this->post('/disputes/{disputeId}', $request, '\SecurionPay\Response\Dispute');
    }

    /**
     * @param  string  $disputeId
     * @return \SecurionPay\Response\Dispute
     */
    public function closeDispute($disputeId)
    {
        return $this->post("/disputes/{$disputeId}/close", null, '\SecurionPay\Response\Dispute');
    }

    /**
     * @param  \SecurionPay\Request\DisputeListRequest  $request
     * @return \SecurionPay\Response\ListResponse
     */
    public function listDisputes($request = null)
    {
        return $this->getList('/disputes', $request, '\SecurionPay\Response\Dispute');
    }

    /**
     * @param  string  $fraudWarningId
     * @return \SecurionPay\Response\Dispute
     */
    public function retrieveFraudWarning($fraudWarningId)
    {
        return $this->get("/fraud-warnings/{$fraudWarningId}", '\SecurionPay\Response\FraudWarning');
    }

    /**
     * @param  \SecurionPay\Request\FraudWarningListRequest  $request
     * @return \SecurionPay\Response\ListResponse
     */
    public function listFraudWarnings($request = null)
    {
        return $this->getList('/fraud-warnings', $request, '\SecurionPay\Response\FraudWarning');
    }

    /**
     * @param  string  $refundId
     * @return \SecurionPay\Response\Refund
     */
    public function retrieveRefund($refundId)
    {
        return $this->get("/refunds/{$refundId}", '\SecurionPay\Response\Refund');
    }

    /**
     * @param  \SecurionPay\Request\RefundRequest  $request
     * @return \SecurionPay\Response\Refund
     */
    public function createRefund($request)
    {
        return $this->post('/refunds', $request, '\SecurionPay\Response\Refund');
    }

    /**
     * @param  \SecurionPay\Request\RefundListRequest  $request
     * @return \SecurionPay\Response\ListResponse
     */
    public function listRefunds($request)
    {
        return $this->getList('/refunds', $request, '\SecurionPay\Response\Refund');
    }

    /**
     * @param  string  $payoutId
     * @return \SecurionPay\Response\Payout
     */
    public function retrievePayout($payoutId)
    {
        return $this->get("/payouts/{$payoutId}", '\SecurionPay\Response\Payout');
    }

    /**
     * @return \SecurionPay\Response\Payout
     */
    public function createPayout()
    {
        return $this->post('/payouts', null, '\SecurionPay\Response\Payout');
    }

    /**
     * @param  \SecurionPay\Request\PayoutListRequest  $request
     * @return \SecurionPay\Response\ListResponse
     */
    public function listPayouts($request = null)
    {
        return $this->getList('/payouts', $request, '\SecurionPay\Response\Payout');
    }

    /**
     * @param  \SecurionPay\Request\PayoutTransactionListRequest  $request
     * @return \SecurionPay\Response\ListResponse
     */
    public function listPayoutTransactions($request)
    {
        return $this->getList('/payout-transactions', $request, '\SecurionPay\Response\PayoutTransaction');
    }

    /**
     * @param  \SecurionPay\Request\CheckoutRequest  $request
     * @return string
     */
    public function signCheckoutRequest($request)
    {
        $path = '';
        $data = $this->objectSerializer->serialize($request, $path);

        $signarute = hash_hmac('sha256', $data, $this->privateKey);

        return base64_encode($signarute.'|'.$data);
    }

    private function get($path, $responseClass)
    {
        return $this->getFromEndpoint($this->endpoint, $path, $responseClass);
    }

    private function getFromEndpoint($endpoint, $path, $responseClass)
    {
        $response = $this->connection->get($endpoint.$path, $this->buildHeaders());
        $this->ensureSuccess($response);

        return $this->objectSerializer->deserialize($response['body'], $responseClass);
    }

    private function post($path, $request, $responseClass)
    {
        $requestBody = $this->objectSerializer->serialize($request, $path);
        $response = $this->connection->post($this->endpoint.$path, $requestBody, $this->buildHeaders());
        $this->ensureSuccess($response);

        return $this->objectSerializer->deserialize($response['body'], $responseClass);
    }

    private function multipart($path, $files, $form, $responseClass)
    {
        $response = $this->connection->multipart($this->uploadsEndpoint.$path, $files, $form, $this->buildHeaders());
        $this->ensureSuccess($response);

        return $this->objectSerializer->deserialize($response['body'], $responseClass);
    }

    private function getList($path, $request, $elementClass)
    {
        return $this->listFromEndpoint($this->endpoint, $path, $request, $elementClass);
    }

    private function listFromEndpoint($endpoint, $path, $request, $elementClass)
    {
        $url = $this->buildQueryString($endpoint.$path, $request);
        $response = $this->connection->get($url, $this->buildHeaders());
        $this->ensureSuccess($response);

        return $this->objectSerializer->deserializeList($response['body'], $elementClass);
    }

    private function delete($path, $request, $responseClass)
    {
        $url = $this->endpoint.$this->buildQueryString($path, $request);
        $response = $this->connection->delete($url, $this->buildHeaders());
        $this->ensureSuccess($response);

        return $this->objectSerializer->deserialize($response['body'], $responseClass);
    }

    private function ensureSuccess($response)
    {
        if ($response['status'] != 200) {
            $error = $this->objectSerializer->deserialize($response['body'], '\SecurionPay\Response\ErrorResponse');
            throw new SecurionPayException($error);
        }

        return $response;
    }

    private function buildQueryString($path, $request)
    {
        if ($request == null) {
            return $path;
        }

        $queryString = $this->objectSerializer->serializeToQueryString($request, $path);

        return $path.$queryString;
    }

    private function buildHeaders()
    {
        return [
            'Authorization' => 'Basic '.base64_encode($this->privateKey.':'),
            'Content-Type' => 'application/json',
            'User-Agent' => ($this->userAgent ? $this->userAgent.' ' : '').'SecurionPay-PHP/'.self::VERSION.' (PHP/'.phpversion().')',
        ];
    }

    public function setPrivateKey($privateKey)
    {
        $this->privateKey = $privateKey;
    }

    public function setConnection(Connection $connection)
    {
        $this->connection = $connection;
    }

    public function setEndpoint($endpoint)
    {
        $this->endpoint = $endpoint;
    }

    public function setUploadsEndpoint($uploadsEndpoint)
    {
        $this->uploadsEndpoint = $uploadsEndpoint;
    }

    public function setUserAgent($userAgent)
    {
        $this->userAgent = $userAgent;
    }
}
