<?php

class Agro_CepAuxiliar
{
    private const ORIGEM_API_CEP_VIACEP = 1;
    private const ORIGEM_API_OPENCEP = 2;
    private const ORIGEM_API_BRASILAPI = 3;
    private const ORIGEM_API_CEP_CEPABERTO = 4;

    private $address = array();
    private bool $ambienteProducao;
    private $cep = '';
    private $parameters = '';
    private $result = '';
    private array $urls = [
        self::ORIGEM_API_CEP_VIACEP => API_CEP_VIACEP,
        self::ORIGEM_API_OPENCEP => API_CEP_OPENCEP,
        self::ORIGEM_API_CEP_CEPABERTO => API_CEP_CEPABERTO,
        self::ORIGEM_API_BRASILAPI => API_CEP_BRASILAPI,
    ];

    public function __construct()
    {
        $this->ambienteProducao = APPLICATION_ENV === 'production';
    }

    public function setCep(string $cep): void
    {
        $formated_cep = $this->formatarCep($cep);

        if (!preg_match('/^[0-9]{8}?$/', $formated_cep)) {
            throw new \Exception("CEP inválido");
        }

        $this->cep = $formated_cep;
        $this->cepFormatoOriginal = $cep;
    }

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

    public function getAddress(): string
    {
        return implode('/', $this->address);
    }

    public function setAddress(string $uf, string $cidade, string $logradouro): void
    {
        if (strlen($uf) > 2) {
            throw new \Exception("UF inválida");
        }

        $this->address = array(rawurlencode($uf), rawurlencode($cidade), rawurlencode($logradouro));
    }

    public function setParameters(string $parameters): void
    {
        $this->parameters = $parameters;
    }

    public function getParameters(): string
    {
        //formato de retorno fixo em json
        return $this->parameters . '/json';
    }

    /**
     * @param string $url
     * @param int $api
     * @return false|array
     */
    private function makeRequest(string $url, int $api)
    {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        /**
         * Descomentado somente em ambientes de teste e local devido a erro na validação de SSL nestes ambientes para
         * determinadas API's
         */
        if (!$this->ambienteProducao) {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_TIMEOUT, 10);
        }

        if ($api === self::ORIGEM_API_CEP_CEPABERTO) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: Token token="' . TOKEN_API_CEP_CEPABERTO . '"'));
        }

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        return $httpCode === 200 ? json_decode($response, true) : false;
    }

    private function buildCall()
    {
        foreach ($this->urls as $api => $url) {
            $urlCompleta = $url;

            switch ($api) {
                case self::ORIGEM_API_CEP_VIACEP:
                    $urlCompleta .= ($this->getCep() != '' ? $this->getCep() : $this->getAddress()) . $this->getParameters();
                    break;

                case self::ORIGEM_API_OPENCEP:
                case self::ORIGEM_API_BRASILAPI:
                    if (empty($this->getCep())) {
                        continue 2;
                    }

                    $urlCompleta .= $this->getCep();

                    break;

                case self::ORIGEM_API_CEP_CEPABERTO:
                    if (empty($this->getCep())) {
                        continue 2;
                    }

                    $urlCompleta .= "cep?cep={$this->getCep()}";
                    break;
            }

            $this->result = $this->makeRequest($urlCompleta, $api);

            if (!empty($this->result)) {
                if (isset($this->result['erro']) || isset($this->result['error'])) {
                    $this->result = '';
                    continue;
                }

                $this->deParaPropriedades($api);

                break;
            }
        }

        if (empty($this->result)) {
            throw new Exception('Não foi possível encontrar as informações.');
        }
    }

    public function getCepInfo(): ?array
    {
        $this->buildCall();
        $response = $this->result;

        if (count($response) == 0 && $response['erro'] != true) {
            $this->address[1] = $this->addApostrofo($this->address[1]);
            if ($this->address[1] != '') {
                $this->buildCall();
                $response = json_decode($this->result, true);
            }
        }

        if (count($response) == 0 || $response['erro'] == true) {
            throw new Exception('Nenhum resultado encontrado.');
        }

        if (array_key_exists('cep', $response)) {
            $response = $this->getEstadoMunicipio($response);
        } else {
            foreach ($response as $key => $data) {
                $response[$key] = $this->getEstadoMunicipio($data);
            }
        }

        return $response;
    }

    private function getEstadoMunicipio(array $data): array
    {
        $data['id_estado'] = (new Sistema_Model_Estados())->getId($data['uf']);

        //turn around para palavras com apostrofos
        if (strpos($data['localidade'], '\'' === false)) {
            $rowMunicipio = (new Sistema_Model_Municipios())->fetchRow(
                ['upper(unaccent(ds_nome_municipio)) = upper(unaccent(\'' . $data['localidade'] . '\'))', "id_estado = {$data['id_estado']}"]
            );
        } else {
            $rowMunicipio = (new Sistema_Model_Municipios())->fetchRow(
                ['upper(unaccent(ds_nome_municipio)) like upper(unaccent(\'' . str_replace('\'', '%', $data['localidade']) . '\'))', "id_estado = {$data['id_estado']}"]
            );
        }

        if (empty($rowMunicipio)) {
            throw new Exception('Município não encontrado.');
        }

        $data['id_municipio'] = $rowMunicipio->id;

        return $data;
    }

    private function addApostrofo(string $input): string
    {
        //adiciona apostrofo pois no banco todos foram removidos
        preg_match("/[[:space:]]d[aeo][[:space:]][aeo]/i", urldecode($input), $matches);

        if (count($matches) > 0) {
            $char = explode(' ', $matches[0]);
            return str_replace('+', '%20', urlencode(str_replace($matches[0], " d'$char[2]", urldecode($input))));
        }

        return '';
    }

    private function deParaPropriedades(int $api): void
    {
        $dePara = [
            self::ORIGEM_API_BRASILAPI => [
                'cep' => $this->result['cep'],
                'logradouro' => $this->result['street'],
                'bairro' => $this->result['neighborhood'],
                'localidade' => $this->result['city'],
                'uf' => $this->result['state'],
            ],
            self::ORIGEM_API_CEP_CEPABERTO => [
                'cep' => !empty($this->result['cep']) ? $this->formatarCep($this->result['cep']) : '',
                'logradouro' => $this->result['logradouro'],
                'bairro' => $this->result['bairro'],
                'localidade' => $this->result['cidade']['nome'],
                'uf' => $this->result['estado']['sigla'],
            ],
        ];

        if (array_key_exists($api, $dePara)) {
            $this->result = $dePara[$api];
        }
    }

    private function formatarCep(string $cep): string
    {
        return preg_replace("/[^0-9]/", "", $cep);
    }
}