<?php

namespace App\Services\Sefaz;

use App\Models\Company;
use Carbon\Carbon;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use NFePHP\Common\Certificate;
use RuntimeException;

/**
 * Guarda e valida certificados digitais A1 (.pfx / .p12).
 *
 * O arquivo vai para um disco privado fora do document root e a senha é
 * persistida com o cast "encrypted" do Eloquent (AES-256 com a APP_KEY).
 */
class CertificateService
{
    /**
     * Lê os metadados do PFX sem persistir nada.
     *
     * @return array{cnpj: ?string, subject: string, valid_from: Carbon, valid_to: Carbon}
     */
    public function inspect(string $pfxContent, string $password): array
    {
        $certs = [];

        if (! openssl_pkcs12_read($pfxContent, $certs, $password)) {
            throw new RuntimeException('Não foi possível abrir o certificado. Verifique se o arquivo é um A1 válido e se a senha está correta.');
        }

        $parsed = openssl_x509_parse($certs['cert']);

        if ($parsed === false) {
            throw new RuntimeException('O certificado não pôde ser interpretado.');
        }

        $subject = $parsed['subject']['CN'] ?? '';

        return [
            'cnpj' => $this->extractCnpj($parsed),
            'subject' => $subject,
            'valid_from' => Carbon::createFromTimestampUTC($parsed['validFrom_time_t']),
            'valid_to' => Carbon::createFromTimestampUTC($parsed['validTo_time_t']),
        ];
    }

    /**
     * Valida, grava o arquivo e atualiza a empresa.
     */
    public function store(Company $company, UploadedFile $file, string $password): array
    {
        $content = file_get_contents($file->getRealPath());

        if ($content === false) {
            throw new RuntimeException('Falha ao ler o arquivo enviado.');
        }

        $info = $this->inspect($content, $password);

        if ($info['valid_to']->isPast()) {
            throw new RuntimeException('Este certificado está vencido em '.$info['valid_to']->format('d/m/Y').'.');
        }

        if ($info['cnpj'] && $info['cnpj'] !== $company->cnpj) {
            throw new RuntimeException(sprintf(
                'O certificado pertence ao CNPJ %s, diferente do CNPJ da empresa (%s).',
                Company::formatCnpj($info['cnpj']),
                $company->cnpj_formatado,
            ));
        }

        $path = sprintf('%s/%s.pfx', $company->id, bin2hex(random_bytes(12)));

        Storage::disk('certificados')->put($path, $content);

        $this->forget($company);

        $company->forceFill([
            'cert_path' => $path,
            'cert_password' => $password,
            'cert_cnpj' => $info['cnpj'],
            'cert_subject' => $info['subject'],
            'cert_valid_from' => $info['valid_from'],
            'cert_valid_to' => $info['valid_to'],
        ])->save();

        return $info;
    }

    /**
     * Instancia o certificado da sped-nfe para uso nas chamadas SOAP.
     */
    public function load(Company $company): Certificate
    {
        if (! $company->hasCertificate()) {
            throw new RuntimeException('A empresa não possui certificado digital cadastrado.');
        }

        if ($company->certificateExpired()) {
            throw new RuntimeException('O certificado digital da empresa está vencido.');
        }

        return Certificate::readPfx(
            Storage::disk('certificados')->get($company->cert_path),
            (string) $company->cert_password,
        );
    }

    /**
     * Remove o certificado atual do disco e limpa os campos.
     */
    public function forget(Company $company, bool $persist = false): void
    {
        if ($company->cert_path && Storage::disk('certificados')->exists($company->cert_path)) {
            Storage::disk('certificados')->delete($company->cert_path);
        }

        if ($persist) {
            $company->forceFill([
                'cert_path' => null,
                'cert_password' => null,
                'cert_cnpj' => null,
                'cert_subject' => null,
                'cert_valid_from' => null,
                'cert_valid_to' => null,
            ])->save();
        }
    }

    /**
     * Nos certificados ICP-Brasil de pessoa jurídica o CNPJ do titular vem no
     * CN, no formato "EMPRESA LTDA:00000000000000". Quando não estiver lá,
     * devolvemos null e a conferência contra o CNPJ da empresa é ignorada —
     * é preferível a um falso negativo extraído de outras extensões, que
     * carregam CPF/NIS/RG concatenados e produziriam bloqueios indevidos.
     */
    private function extractCnpj(array $parsed): ?string
    {
        $cn = $parsed['subject']['CN'] ?? '';

        return preg_match('/:(\d{14})\s*$/', $cn, $m) ? $m[1] : null;
    }
}
