<?php

namespace WPDesk\FakturyKSEF\Api\Services;

use WPDesk\FakturyKSEF\Api\AccessTokenService;
use WPDesk\FakturyKSEF\Api\Exceptions\AuthenticationException;
use WPDesk\FakturyKSEF\Api\LowLevelAPIClient;
use WPDesk\FakturyKSEF\Api\Requests\GetAuthStatusRequest;
use WPDesk\FakturyKSEF\Api\Requests\GetChallengeRequest;
use WPDesk\FakturyKSEF\Api\Requests\GetKSeFAuthTokenRequest;
use WPDesk\FakturyKSEF\Api\Requests\GetKSeFTokensRequest;
use WPDesk\FakturyKSEF\Api\Requests\RefreshTokensRequest;
use WPDesk\FakturyKSEF\Services\EncryptionService;
use WPDeskKSEFVendor\GuzzleHttp\Exception\GuzzleException;
use WPDeskKSEFVendor\GuzzleHttp\Exception\RequestException;

class AuthService {

	private string $nip;

	private string $api_token;

	private EncryptionService $encryption_service;

	private AccessTokenService $token_storage;

	private LowLevelAPIClient $http_client;

	private CertificateService $certificate_service;

	public function __construct(
		string $nip,
		string $api_token,
		LowLevelAPIClient $http_client,
		EncryptionService $encryption_service,
		AccessTokenService $token_storage,
		CertificateService $certificate_service
	) {
		$this->certificate_service = $certificate_service;
		$this->token_storage       = $token_storage;
		$this->http_client         = $http_client;
		$this->encryption_service  = $encryption_service;
		$this->api_token           = $api_token;
		$this->nip                 = $nip;
	}

	public function authenticate( bool $force_new_session = false ): bool {

		if ( $force_new_session ) {
			$this->clear_auth_data();
		}

		if ( ! $force_new_session && $this->token_storage->has_refresh_token() ) {
			try {
				$tokens = $this->refresh_access_token( $this->token_storage->get_refresh_token() );

				if ( ! empty( $tokens['refresh_token'] ) ) {
					$this->token_storage->update_refresh_token( $tokens['refresh_token']['token'] );
				}
				if ( ! empty( $tokens['access_token'] ) ) {
					$this->token_storage->update_access_token( $tokens['access_token']['token'] );
				}

				return true;

			} catch ( AuthenticationException $e ) {
				$this->clear_auth_data();
			}
		}

		try {
			$auth_token_data      = $this->get_auth_token();
			$reference_number     = $auth_token_data['referenceNumber'];
			$authentication_token = $auth_token_data['authenticationToken']['token'];

			$this->check_auth_status( $reference_number, $authentication_token );

			$tokens        = $this->http_client->execute( new GetKSeFTokensRequest(), $authentication_token );
			$access_token  = $tokens['accessToken']['token'] ?? null;
			$refresh_token = $tokens['refreshToken']['token'] ?? null;
		} catch ( \Exception | GuzzleException $e ) {
			$this->clear_auth_data();
			return false;
		}

		if ( empty( $access_token ) || empty( $refresh_token ) ) {
			$this->clear_auth_data();
			return false;
		}

		$this->token_storage->update_access_token( $access_token );
		$this->token_storage->update_refresh_token( $refresh_token );

		return true;
	}

	private function check_auth_status( string $reference_number, string $authentication_token ): void {

		$response = $this->http_client->execute( new GetAuthStatusRequest( $reference_number ), $authentication_token );

		if ( isset( $response['status']['code'] ) && $response['status']['code'] === 200 ) {
			return;
		}

		throw new AuthenticationException( esc_html__( 'Failed to authenticate connection with KSeF.', 'ksef-for-flexible-invoices' ) );
	}

	public function get_access_token(): string {
		return $this->token_storage->get_access_token();
	}

	private function get_auth_token(): array {

		$challenge_data = $this->http_client->execute( new GetChallengeRequest() );
		$challenge      = $challenge_data['challenge'];
		$timestamp      = $challenge_data['timestamp'];

		$public_key = $this->encryption_service->get_public_key_from_certificates( $this->certificate_service->get_certificates(), CertificateService::PUBLIC_KEY_KSEF_TOKEN_ENCRYPTION );

		$date_time    = new \DateTime( $timestamp );
		$timestamp_ms = (int) floor( $date_time->format( 'U.u' ) * 1000 );

		$token_to_encrypt = $this->api_token . '|' . $timestamp_ms;
		$encrypted_token  = $this->encryption_service->encrypt_key( $token_to_encrypt, $public_key );
		return $this->http_client->execute( new GetKSeFAuthTokenRequest( $this->nip, $challenge, base64_encode( $encrypted_token ) ) ); //phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
	}


	public function refresh_access_token( string $refresh_token ): array {
		try {
			$response = $this->http_client->execute( new RefreshTokensRequest(), $refresh_token );

			if ( empty( $response['accessToken'] ) ) {
				throw new AuthenticationException( esc_html__( 'Failed to refresh token.', 'ksef-for-flexible-invoices' ) );
			}

			return [
				'access_token'  => $response['accessToken'],
				'refresh_token' => $response['refreshToken'] ?? null,
			];

		} catch ( RequestException $e ) {
			// translators: error message.
			throw new AuthenticationException( sprintf( esc_html__( 'Unable to refresh token. It may have expired. Reauthentication required. Error: %s', 'ksef-for-flexible-invoices' ), $e->getMessage() ) );
		}
	}


	public function has_access_token(): bool {
		return $this->token_storage->has_access_token();
	}

	private function clear_auth_data(): void {
		$this->token_storage->clear_tokens();
		$this->certificate_service->clear_certificates();
	}
}
