<?php

defined('ABSPATH') || exit;

class Stitch_Express_Exception extends Exception {
    public function __construct(string $message, int $code = 0) {
        parent::__construct('[Stitch Express Exception] '.$message, $code);
    }
}

class Stitch_Express_Failed_Request_Exception extends Stitch_Express_Exception {
    public function __construct(int $status_code, string $response_body) {
        parent::__construct("Request failed: ({$status_code}) {$response_body}");
    }
}

class Stitch_Express_Invalid_Request_Exception extends Stitch_Express_Exception {}

class Stitch_Express_Failed_Authentication_Exception extends Stitch_Express_Exception {
    public function __construct(int $status_code, string $response_body) {
        parent::__construct("Authentication failed: ({$status_code}) {$response_body}");
    }
}

class Stitch_Express_Response {
    public int $status_code;
    public bool $is_error;
    public ?array $data;
    public string $raw_body;

    public function __construct(string $raw_response, int|string $status_code) {
        $this->status_code = gettype($status_code) === 'string' ? 0 : $status_code;
        $this->raw_body = $raw_response;
        $this->data = json_decode($raw_response, true);
        $this->is_error = $status_code >= 500;
    }
}

class Stitch_Express_Client {
    private const PLUGIN_VERSION = '1.5.0';
    private string $baseUrl = 'https://express.stitch.money';
    private string $client_id;
    private string $client_secret;

    public function __construct(string $client_id, string $client_secret) {
        $this->client_id = $client_id;
        $this->client_secret = $client_secret;
    }

    /**
     * @throws Stitch_Express_Failed_Request_Exception
     * @throws Stitch_Express_Failed_Authentication_Exception
     */
    public function get_payment_status(string $payment_id, ?string $merchant_reference = null): ?string {
        $path = '/api/v1/payments/'.$payment_id;
        if ($merchant_reference !== null) {
            $path .= '?merchantReference='.urlencode($merchant_reference);
        }

        $response = $this->make_request($path, 'GET');

        if ($response->status_code === 404) {
            return null;
        }

        if ($response->is_error) {
            $status_code = $response->status_code;
            $body = $response->raw_body;

            throw new Stitch_Express_Failed_Request_Exception(esc_html($status_code), esc_html($body));
        }

        return $response->data['data']['payment']['status'];
    }

    /**
     * @throws Stitch_Express_Failed_Request_Exception
     * @throws Stitch_Express_Failed_Authentication_Exception
     * @throws Stitch_Express_Invalid_Request_Exception
     */
    public function refund_payment(string $payment_id, int $amount, string $reason) {
        $api_reason = $this->get_api_refund_reason($reason);

        $response = $this->make_request("/api/v1/payment/{$payment_id}/refund", 'POST', [
            'amount' => $amount,
            'reason' => $api_reason,
        ]);

        if ($response->is_error || !$response->data['success']) {
            $status_code = $response->status_code;
            $body = $response->raw_body;

            throw new Stitch_Express_Failed_Request_Exception(esc_html($status_code), esc_html($body));
        }

        return $response->data['data']['refund'];
    }

    /**
     * @throws Stitch_Express_Failed_Request_Exception
     * @throws Stitch_Express_Failed_Authentication_Exception
     */
    public function confirm_delivery(string $payment_id, string $merchant_reference): void {
        $response = $this->make_request("/api/v1/payments/{$payment_id}/order-confirmed", 'POST', [
            'merchantReference' => $merchant_reference,
        ]);

        if (!$response->data['success']) {
            throw new Stitch_Express_Failed_Request_Exception($response->status_code, $response->raw_body);
        }
    }

    /**
     * @throws Stitch_Express_Failed_Request_Exception
     * @throws Stitch_Express_Exception
     */
    public function create_payment_link(
        int $amount,
        string $payer_name,
        ?string $payer_phone,
        ?string $payer_email,
        string $merchant_ref,
        bool $skip_checkout_page
    ): string {
        $response = $this->make_request('/api/v1/payments', 'POST', [
            'amount' => $amount,
            'payerName' => $payer_name,
            'payerPhoneNumber' => $payer_phone,
            'payerEmailAddress' => $payer_email,
            'merchantReference' => $merchant_ref,
            'skipCheckoutPage' => $skip_checkout_page,
        ]);

        if ($response->is_error || !$response->data['success']) {
            $status_code = $response->status_code;
            $body = $response->raw_body;

            throw new Stitch_Express_Failed_Request_Exception(esc_html($status_code), esc_html($body));
        }

        return $response->data['data']['payment']['link'];
    }

    /**
     * @throws Stitch_Express_Failed_Request_Exception
     * @throws Stitch_Express_Failed_Authentication_Exception
     */
    public function register_domain(string $domain): void {
        $this->make_request('/api/v1/register-woo-commerce-domain', 'POST', [
            'domain' => $domain,
        ]);
    }

    /**
     * @throws Stitch_Express_Failed_Request_Exception
     * @throws Stitch_Express_Failed_Authentication_Exception
     */
    private function make_request(
        string $path,
        string $method,
        array $data = [],
        bool $withToken = true
    ): Stitch_Express_Response {
        $headers = [
            'Content-Type' => 'application/json',
            'X-IS-WC' => 'true',
            'X-Stitch-Express-WC-Plugin-Version' => self::PLUGIN_VERSION,
        ];

        if ($withToken) {
            $token = $this->get_token();
            $headers['Authorization'] = 'Bearer '.$token;
        }

        $result = wp_remote_request($this->baseUrl.$path, [
            'method' => $method,
            'headers' => $headers,
            'body' => $method !== 'GET' ? wp_json_encode($data) : null,
        ]);

        $body = wp_remote_retrieve_body($result);
        $response_code = wp_remote_retrieve_response_code($result);
        $response = new Stitch_Express_Response($body, $response_code);

        if (is_wp_error($result) || $response->is_error) {
            $status_code = $response->status_code;
            $body = $response->raw_body;

            throw new Stitch_Express_Failed_Request_Exception(esc_html($status_code), esc_html($body));
        }

        return $response;
    }

    /**
     * @throws Stitch_Express_Failed_Request_Exception
     * @throws Stitch_Express_Failed_Authentication_Exception
     */
    private function get_token(): string {
        $response = $this->make_request('/api/v1/token', 'POST', [
            'clientId' => $this->client_id,
            'clientSecret' => $this->client_secret,
        ], false);

        if ($response->status_code !== 200 || $response->is_error) {
            $status_code = $response->status_code;
            $body = $response->raw_body;

            throw new Stitch_Express_Failed_Authentication_Exception(esc_html($status_code), esc_html($body));
        }

        return $response->data['data']['accessToken'];
    }

    private function get_api_refund_reason(string $reason): string {
        switch (strtolower($reason)) {
            case 'requested by user':
                return 'requested_by_user';

            case 'duplicate':
                return 'duplicate';

            case 'fraud':
                return 'fraudulent';

            default:
                throw new Stitch_Express_Invalid_Request_Exception("Invalid refund reason {$reason}");
        }
    }
}
