<?php


namespace Twilio\Http;


use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Psr7\Query;
use GuzzleHttp\Psr7\Request;
use Twilio\Exceptions\HttpException;
use Twilio\AuthStrategy\AuthStrategy;

final class GuzzleClient implements Client {
    /**
     * @var ClientInterface
     */
    private $client;

    public function __construct(ClientInterface $client) {
        $this->client = $client;
    }

    public function request(string $method, string $url,
                            array $params = [], array $data = [], array $headers = [],
                            ?string $user = null, ?string $password = null,
                            ?int $timeout = null, ?AuthStrategy $authStrategy = null): Response {
        try {
            $options = [
                'timeout' => $timeout,
                'allow_redirects' => false,
            ];

            if($user && $password) {
                $headers['Authorization'] = 'Basic ' . base64_encode($user . ':' . $password);
            }
            elseif ($authStrategy) {
                $headers['Authorization'] = $authStrategy->getAuthString();
            }

            if ($params) {
                $options['query'] = Query::build($params, PHP_QUERY_RFC1738);
            }

            if ($method === 'POST' || $method === 'PUT') {
                if ($this->hasFile($data)) {
                    $options['multipart'] = $this->buildMultipartParam($data);
                } else {
                    $options['body'] = Query::build($data, PHP_QUERY_RFC1738);
                    $headers['Content-Type'] = 'application/x-www-form-urlencoded';
                }
            }

            $response = $this->client->send(new Request($method, $url, $headers), $options);
        } catch (BadResponseException $exception) {
            $response = $exception->getResponse();
        } catch (\Exception $exception) {
            throw new HttpException('Unable to complete the HTTP request', 0, $exception);
        }

        // Casting the body (stream) to a string performs a rewind, ensuring we return the entire response.
        // See https://stackoverflow.com/a/30549372/86696
        return new Response($response->getStatusCode(), (string)$response->getBody(), $response->getHeaders());
    }

    private function hasFile(array $data): bool {
        foreach ($data as $value) {
            if ($value instanceof File) {
                return true;
            }
        }

        return false;
    }

    private function buildMultipartParam(array $data): array {
        $multipart = [];
        foreach ($data as $key => $value) {
            if ($value instanceof File) {
                $contents = $value->getContents();
                if ($contents === null) {
                    $contents = fopen($value->getFileName(), 'rb');
                }

                $chunk = [
                    'name' => $key,
                    'contents' => $contents,
                    'filename' => $value->getFileName(),
                ];

                if ($value->getContentType() !== null) {
                    $chunk['headers']['Content-Type'] = $value->getContentType();
                }
            } else {
                $chunk = [
                    'name' => $key,
                    'contents' => $value,
                ];
            }

            $multipart[] = $chunk;
        }

        return $multipart;
    }
}
