<?php

namespace App\Services;

use App\Models\Setting;
use App\Config\Database;

class PangolinService
{
    private $apiUrl;
    private $token;
    private $orgId;
    private $resourceName;
    private $enabled;
    private $resourceIdCache = null;

    public function __construct()
    {
        $settingModel = new Setting();
        $this->apiUrl = rtrim($settingModel->get('pangolin_url') ?? '', '/');
        $this->token = $settingModel->get('pangolin_token');
        $this->orgId = $settingModel->get('pangolin_org_id');
        $this->resourceName = $settingModel->get('pangolin_resource_name');
        $this->enabled = $settingModel->get('pangolin_enabled') === '1';
    }

    public function isEnabled()
    {
        return $this->enabled && !empty($this->apiUrl) && !empty($this->token) && !empty($this->orgId) && !empty($this->resourceName);
    }

    /**
     * Obtém o resourceId numérico a partir do orgId + niceId (resourceName)
     * Busca da lista de resources e filtra pelo niceId
     */
    private function getResourceId()
    {
        if ($this->resourceIdCache !== null) {
            return $this->resourceIdCache;
        }

        // Busca a lista de resources da organização
        $endpoint = "/v1/org/{$this->orgId}/resources";
        $result = $this->callApi('GET', $endpoint);

        if ($result && isset($result['data']['resources']) && is_array($result['data']['resources'])) {
            foreach ($result['data']['resources'] as $resource) {
                if (isset($resource['niceId']) && $resource['niceId'] === $this->resourceName) {
                    $this->resourceIdCache = $resource['resourceId'];
                    $this->log("Resource ID encontrado: " . $this->resourceIdCache . " para niceId: " . $this->resourceName);
                    return $this->resourceIdCache;
                }
            }
            $this->log("Resource com niceId '{$this->resourceName}' não encontrado na lista.");
        }

        $this->log("Falha ao obter Resource ID - Resposta: " . json_encode($result));
        return null;
    }

    /**
     * Obtém os IPs dos servidores ativos de um cliente
     */
    private function getClientServerIps($clientId)
    {
        $ips = [];

        try {
            $conn = Database::getInstance()->getConnection();
            $stmt = $conn->prepare("SELECT ip_v4, ip_v6 FROM servers WHERE client_id = :client_id AND status = 'active'");
            $stmt->execute(['client_id' => $clientId]);
            $servers = $stmt->fetchAll(\PDO::FETCH_ASSOC);

            foreach ($servers as $server) {
                if (!empty($server['ip_v4'])) {
                    $ips[] = $server['ip_v4'];
                }
                if (!empty($server['ip_v6'])) {
                    $ips[] = $server['ip_v6'];
                }
            }
        } catch (\Exception $e) {
            $this->log("Erro ao buscar IPs dos servidores: " . $e->getMessage());
        }

        return $ips;
    }

    /**
     * Sincroniza os IPs dos servidores de um cliente com o Pangolin.
     * @param int $clientId ID do cliente
     * @param string $action 'add' ou 'remove'
     */
    public function syncClient($clientId, $action)
    {
        if (!$this->isEnabled()) {
            $this->log("Integração desabilitada ou mal configurada.");
            return;
        }

        $resourceId = $this->getResourceId();
        if (!$resourceId) {
            $this->log("Não foi possível obter o Resource ID. Abortando sincronização.");
            return;
        }

        $ips = $this->getClientServerIps($clientId);

        if (empty($ips) && $action === 'add') {
            $this->log("Nenhum IP de servidor encontrado para cliente ID: $clientId");
            return;
        }

        if (!empty($ips)) {
            $this->log("Cliente ID $clientId - IPs encontrados: " . count($ips) . " (" . implode(', ', $ips) . ")");
        }

        if ($action === 'add') {
            foreach ($ips as $ip) {
                $this->addRule($resourceId, $ip);
            }
        } elseif ($action === 'remove') {
            foreach ($ips as $ip) {
                $this->removeRule($resourceId, $ip);
            }
        }
    }

    /**
     * Remove IPs específicos do Pangolin (usado quando servidor é deletado/modificado)
     * @param array $ips Lista de IPs a remover
     */
    public function removeServerIps($ips)
    {
        if (!$this->isEnabled()) {
            return;
        }

        $resourceId = $this->getResourceId();
        if (!$resourceId) {
            return;
        }

        foreach ($ips as $ip) {
            if (!empty($ip)) {
                $this->removeRule($resourceId, $ip);
            }
        }
    }

    /**
     * Adiciona uma regra ACCEPT para um IP.
     * PUT /v1/resource/{resourceId}/rule
     */
    private function addRule($resourceId, $ip)
    {
        $endpoint = "/v1/resource/{$resourceId}/rule";
        $data = [
            'action' => 'ACCEPT',
            'match' => 'IP',
            'value' => $ip,
            'priority' => 100,
            'enabled' => true
        ];

        $result = $this->callApi('PUT', $endpoint, $data);

        if ($result && isset($result['success']) && $result['success']) {
            $this->log("Regra adicionada com sucesso para IP: $ip");
        } else {
            $msg = isset($result['message']) ? $result['message'] : json_encode($result);
            $this->log("Falha ao adicionar regra para IP: $ip - $msg");
        }

        return $result;
    }

    /**
     * Remove uma regra baseada no IP.
     * Primeiro lista as regras para encontrar o ID, depois deleta.
     */
    private function removeRule($resourceId, $ip)
    {
        // Listar regras existentes
        $rules = $this->listRules($resourceId);

        if (!$rules || !isset($rules['data']['rules'])) {
            $this->log("Não foi possível listar regras para encontrar IP: $ip");
            return;
        }

        $found = false;
        foreach ($rules['data']['rules'] as $rule) {
            if (
                isset($rule['match']) && $rule['match'] === 'IP' &&
                isset($rule['value']) && $rule['value'] === $ip &&
                isset($rule['ruleId'])
            ) {
                $found = true;
                $this->deleteRule($resourceId, $rule['ruleId']);
            }
        }

        if (!$found) {
            $this->log("Regra para IP $ip não encontrada nas regras existentes.");
        }
    }

    /**
     * Lista todas as regras de um resource.
     */
    private function listRules($resourceId)
    {
        $endpoint = "/v1/resource/{$resourceId}/rules";
        return $this->callApi('GET', $endpoint);
    }

    /**
     * Deleta uma regra pelo ID.
     */
    private function deleteRule($resourceId, $ruleId)
    {
        $endpoint = "/v1/resource/{$resourceId}/rule/{$ruleId}";
        $result = $this->callApi('DELETE', $endpoint);

        if ($result && isset($result['success']) && $result['success']) {
            $this->log("Regra ID $ruleId removida com sucesso.");
        } else {
            $msg = isset($result['message']) ? $result['message'] : json_encode($result);
            $this->log("Falha ao remover regra ID $ruleId - $msg");
        }

        return $result;
    }

    private function callApi($method, $endpoint, $data = [])
    {
        $url = $this->apiUrl . $endpoint;

        $this->log("API Call: [$method] $url");

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Authorization: Bearer ' . $this->token,
            'Content-Type: application/json',
            'Accept: application/json'
        ]);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);

        if (!empty($data)) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
            $this->log("Request Body: " . json_encode($data));
        }

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

        $this->log("Response HTTP $httpCode: " . substr($response, 0, 2000));

        if ($error) {
            $this->log("Curl Error: $error");
            return ['success' => false, 'error' => $error];
        }

        return json_decode($response, true);
    }

    private function log($message)
    {
        try {
            $conn = Database::getInstance()->getConnection();
            $stmt = $conn->prepare("INSERT INTO api_logs (action, message) VALUES ('pangolin_sync', :message)");
            $stmt->execute(['message' => substr($message, 0, 65000)]);
        } catch (\Exception $e) {
            error_log("Pangolin Log Error: " . $e->getMessage());
        }
    }
}
