<?php

declare(strict_types=1);

namespace App\Exceptions\SupportCenter;

use App\Services\SupportCenter\SupportCenterUserFacingErrors;
use RuntimeException;
use Throwable;

/**
 * Support Center Open Host 通信异常（面向用户的文案，不携带密钥）。
 */
final class SupportCenterException extends RuntimeException
{
    public function __construct(
        private readonly string $userFacingMessage,
        private readonly ?string $errorCode = null,
        private readonly ?int $httpStatus = null,
        private readonly ?string $requestId = null,
        private readonly ?string $operation = null,
        ?Throwable $previous = null,
    ) {
        parent::__construct($userFacingMessage, 0, $previous);
    }

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

    public function errorCode(): ?string
    {
        return $this->errorCode;
    }

    public function httpStatus(): ?int
    {
        return $this->httpStatus;
    }

    public function requestId(): ?string
    {
        return $this->requestId;
    }

    public function operation(): ?string
    {
        return $this->operation;
    }

    public static function connectivityBlocked(
        string $reasonCode,
        ?string $operation = null,
        ?SupportCenterUserFacingErrors $errors = null,
    ): self {
        $errors ??= new SupportCenterUserFacingErrors;

        return new self(
            userFacingMessage: $errors->messageFor($reasonCode),
            errorCode: $reasonCode,
            operation: $operation,
        );
    }

    /**
     * @param  array<string, mixed>  $envelope
     */
    public static function fromFailureEnvelope(
        array $envelope,
        ?int $httpStatus = null,
        ?string $operation = null,
        ?SupportCenterUserFacingErrors $errors = null,
    ): self {
        $errors ??= new SupportCenterUserFacingErrors;

        $remoteCode = isset($envelope['error_code']) && is_string($envelope['error_code'])
            ? $envelope['error_code']
            : null;
        $remoteMessage = isset($envelope['message']) && is_string($envelope['message'])
            ? $envelope['message']
            : null;
        $requestId = isset($envelope['request_id']) && is_string($envelope['request_id'])
            ? $envelope['request_id']
            : null;

        $mappedCode = $errors->resolveCode($remoteCode);

        return new self(
            userFacingMessage: $errors->messageFor($mappedCode, $remoteMessage),
            errorCode: $mappedCode,
            httpStatus: $httpStatus,
            requestId: $requestId,
            operation: $operation,
        );
    }

    public static function invalidResponseFormat(
        ?int $httpStatus = null,
        ?string $requestId = null,
        ?string $operation = null,
        ?Throwable $previous = null,
    ): self {
        $errors = new SupportCenterUserFacingErrors;

        return new self(
            userFacingMessage: $errors->messageFor(SupportCenterUserFacingErrors::INVALID_RESPONSE_FORMAT),
            errorCode: SupportCenterUserFacingErrors::INVALID_RESPONSE_FORMAT,
            httpStatus: $httpStatus,
            requestId: $requestId,
            operation: $operation,
            previous: $previous,
        );
    }

    public static function networkTimeout(?string $operation = null, ?Throwable $previous = null): self
    {
        $errors = new SupportCenterUserFacingErrors;

        return new self(
            userFacingMessage: $errors->messageFor(SupportCenterUserFacingErrors::NETWORK_TIMEOUT),
            errorCode: SupportCenterUserFacingErrors::NETWORK_TIMEOUT,
            operation: $operation,
            previous: $previous,
        );
    }

    public static function remoteServiceUnavailable(
        ?int $httpStatus = null,
        ?string $requestId = null,
        ?string $operation = null,
        ?Throwable $previous = null,
    ): self {
        $errors = new SupportCenterUserFacingErrors;

        return new self(
            userFacingMessage: $errors->messageFor(SupportCenterUserFacingErrors::REMOTE_SERVICE_UNAVAILABLE),
            errorCode: SupportCenterUserFacingErrors::REMOTE_SERVICE_UNAVAILABLE,
            httpStatus: $httpStatus,
            requestId: $requestId,
            operation: $operation,
            previous: $previous,
        );
    }

    public static function entitlementBlocked(
        string $userFacingMessage,
        ?string $operation = null,
    ): self {
        return new self(
            userFacingMessage: $userFacingMessage,
            errorCode: 'entitlement_blocked',
            operation: $operation,
        );
    }
}
