package Dinstar::CallForward;

# Módulo para configurar call forwarding incondicional (unconditional)
# en gateways Dinstar UC2000 vía su HTTP API.
#
# ============================================================
# TODO — CONFIRMAR ANTES DE USAR:
#   1) EL ENDPOINT exacto para setear call forwarding.
#      En la doc pública de UC2000 esta función se llama
#      "Call Forward" y se configura por puerto/slot. La HTTP API
#      de Dinstar suele exponer algo del estilo:
#         POST http://<ip>/api/set_port_cfg
#      o bien un endpoint dedicado tipo:
#         POST http://<ip>/api/call_forward
#      Buscá en el PDF "HTTP API" de tu firmware la sección
#      "Call Forward" / "Port Configuration" y pegame el nombre
#      real del endpoint.
#
#   2) LOS NOMBRES DE CAMPO del JSON body. Habitualmente incluyen
#      algo como: port, slot, cfu_enable (o call_forward_enable),
#      cfu_number (o forward_number). Necesito los nombres reales
#      tal como figuran en la tabla de parámetros del PDF.
#
# Una vez que me pases eso, reemplazo el bloque marcado más abajo
# y el módulo queda listo para producción.
# ============================================================

use strict;
use warnings;
use LWP::UserAgent;
use HTTP::Request;
use JSON::PP qw(encode_json decode_json);
use MIME::Base64 qw(encode_base64);

sub new {
    my ($class, %args) = @_;

    my $self = {
        ip       => $args{ip}       || die "Falta ip del gateway Dinstar\n",
        user     => $args{user}     || 'admin',
        password => $args{password} || die "Falta password del gateway Dinstar\n",
        timeout  => $args{timeout}  || 10,
        scheme   => $args{https} ? 'https' : 'http',
    };

    $self->{ua} = LWP::UserAgent->new(timeout => $self->{timeout});

    return bless $self, $class;
}

# Setea desvío incondicional de todas las llamadas entrantes
# de un puerto/slot GSM hacia un número destino.
#
# $port  -> número de puerto físico (ej: 1)
# $slot  -> slot del SIM dentro del puerto (ej: 1), según modelo
# $number -> número destino del forwarding (ej: "5493411234567")
# $enable -> 1 para activar, 0 para desactivar
# $verify -> 1 (default) para releer y confirmar que quedó aplicado
#
# IMPORTANTE: un 200 OK del gateway no garantiza que el valor haya
# quedado seteado (puede fallar silenciosamente si el módulo GSM
# está ocupado, el número es inválido para ese firmware, etc.).
# Por eso, salvo que se pida explícitamente lo contrario, esta
# función relee el estado después de escribir y confirma.
sub set_unconditional_forward {
    my ($self, %args) = @_;

    my $port    = $args{port}    // die "Falta port\n";
    my $slot    = $args{slot}    // die "Falta slot\n";
    my $number  = $args{number};
    my $enable  = defined $args{enable} ? $args{enable} : 1;
    my $verify  = defined $args{verify} ? $args{verify} : 1;

    die "Falta number para activar el forwarding\n" if $enable && !$number;

    # ------------------------------------------------------------
    # BLOQUE A REEMPLAZAR CON EL ENDPOINT/CAMPOS REALES (ver TODO)
    # ------------------------------------------------------------
    my $endpoint = "$self->{scheme}://$self->{ip}/api/call_forward"; # <-- CONFIRMAR

    my $body = {
        port           => $port,
        slot           => $slot,
        cfu_enable     => $enable ? 1 : 0,   # <-- CONFIRMAR nombre de campo
        cfu_number     => $number // '',      # <-- CONFIRMAR nombre de campo
    };
    # ------------------------------------------------------------

    my $result = $self->_post_json($endpoint, $body);

    return $result unless $verify;

    # Confirmar contra el propio gateway que el cambio quedó aplicado
    my $confirmed = $self->get_forward_status(port => $port, slot => $slot);

    my $expected_number = $enable ? $number : '';
    my $ok = ($confirmed->{enabled} == $enable)
          && (!$enable || $confirmed->{number} eq $number);

    unless ($ok) {
        die sprintf(
            "Verificacion fallo en Dinstar %s puerto %s/slot %s: "
          . "se pidio enabled=%s number=%s, pero el gateway devuelve enabled=%s number=%s\n",
            $self->{ip}, $port, $slot,
            $enable, $expected_number,
            $confirmed->{enabled}, $confirmed->{number},
        );
    }

    return $confirmed;
}

# Lee el estado actual de call forwarding de un puerto/slot ANTES
# de tocar nada. Devuelve un hashref normalizado:
#   { enabled => 0|1, number => '...' }
#
# Usar esto siempre antes de set_unconditional_forward para:
#  - no pisar una config existente sin saberlo
#  - evitar llamadas de escritura innecesarias si ya está como querés
#  - loguear el cambio real (antes/después)
sub get_forward_status {
    my ($self, %args) = @_;

    my $port = $args{port} // die "Falta port\n";
    my $slot = $args{slot} // die "Falta slot\n";

    # ------------------------------------------------------------
    # CONFIRMAR: endpoint real de consulta de config del puerto.
    # Suele ser GET (o a veces POST) contra algo tipo:
    #   GET http://<ip>/api/get_port_cfg?port=1&slot=1
    # o un endpoint dedicado tipo /api/call_forward (mismo path
    # que el set, pero con método GET).
    # ------------------------------------------------------------
    my $endpoint = "$self->{scheme}://$self->{ip}/api/call_forward" # <-- CONFIRMAR
                 . "?port=$port&slot=$slot";

    my $data = $self->_get_json($endpoint);

    # ------------------------------------------------------------
    # CONFIRMAR: nombres de campo reales en la respuesta.
    # Ajustar el mapeo de abajo una vez confirmado.
    # ------------------------------------------------------------
    return {
        enabled => $data->{cfu_enable} ? 1 : 0,   # <-- CONFIRMAR
        number  => $data->{cfu_number} // '',      # <-- CONFIRMAR
    };
}

sub _get_json {
    my ($self, $url) = @_;

    my $req = HTTP::Request->new(GET => $url);

    my $auth = encode_base64("$self->{user}:$self->{password}", '');
    $req->header('Authorization' => "Basic $auth");

    my $res = $self->{ua}->request($req);

    unless ($res->is_success) {
        die sprintf(
            "Error HTTP %s al consultar %s: %s\n",
            $res->code, $url, $res->decoded_content // ''
        );
    }

    my $data;
    eval { $data = decode_json($res->decoded_content); 1 }
        or die "Respuesta no es JSON válido: " . $res->decoded_content . "\n";

    return $data;
}

sub _post_json {
    my ($self, $url, $body) = @_;

    my $req = HTTP::Request->new(POST => $url);
    $req->header('Content-Type' => 'application/json');

    # Dinstar HTTP API suele usar Basic Auth
    my $auth = encode_base64("$self->{user}:$self->{password}", '');
    $req->header('Authorization' => "Basic $auth");

    $req->content(encode_json($body));

    my $res = $self->{ua}->request($req);

    unless ($res->is_success) {
        die sprintf(
            "Error HTTP %s al llamar %s: %s\n",
            $res->code, $url, $res->decoded_content // ''
        );
    }

    my $data;
    eval { $data = decode_json($res->decoded_content); 1 }
        or die "Respuesta no es JSON válido: " . $res->decoded_content . "\n";

    return $data;
}

1;