#!/usr/bin/perl
#
# Синхронизация метаданных устройств в Wiki через API
#
# Запуск: perl /path/to/update_wiki_eye.pl
#

use utf8;
use warnings;
use Encode;
use open qw(:std :encoding(UTF-8));
no warnings 'utf8';

use strict;
use English;
use lib "/opt/Eye/scripts";
use FindBin '$Bin';
use eyelib::config;
use Data::Dumper;
use File::Find;
use File::Basename;
use Fcntl qw(:flock);
use LWP::UserAgent;
use HTTP::Request;
use JSON::XS;
use URI::Escape;
use Encode qw(decode_utf8 encode_utf8);

# Блокировка для предотвращения параллельных запусков
open(SELF, "<", $0) or die "Cannot open $0 - $!";
flock(SELF, LOCK_EX | LOCK_NB) or exit 1;

# Настройка API - аутентификация через параметры
my $api_base = $config_ref{api_base} || 'http://localhost/api.php';
my $api_key  = $config_ref{api_key}  || die "Ошибка: не настроен параметр api_key в конфигурации\n";
my $api_login = $config_ref{api_login} || die "Ошибка: не настроен параметр api_login в конфигурации\n";

# Базовые параметры аутентификации для всех запросов
my $auth_params = "api_key=" . uri_escape($api_key) . "&api_login=" . uri_escape($api_login);

# Инициализация HTTP-клиента
my $ua = LWP::UserAgent->new(
    timeout => 30,
    agent   => 'EyeWikiSync/1.0'
);

my $api_request_url = $api_base."?".$auth_params;

my $option_filter = encode_json({ option_id => '61' });

# === Получение пути к вики из таблицы config (id=61) ===
my $api_request = $api_request_url."&get=table_record&table=config&filter=".uri_escape($option_filter);

my $config_response = api_call($ua, 'GET', $api_request);
if ($config_response->{error}) {
    die "Ошибка: не удалось получить путь к вики из таблицы config (id=61): " . $config_response->{error} . "\n";
}

my $wiki_path = $config_response->{data}->{value};
if (!$wiki_path || !-d $wiki_path) {
    die "Ошибка: путь к вики не настроен или директория не существует: $wiki_path\n";
}

print "Путь к вики: $wiki_path\n\n";

# Поиск подходящих файлов
my %content;
find(\&wanted, $wiki_path);

my $processed = 0;
my $errors    = 0;

foreach my $fname (sort keys %content) {
    print "Analyze $content{$fname}...";
    # Чтение файла
    open(my $fh, '<:encoding(UTF-8)', $content{$fname}) or do {
        warn "Не удалось открыть файл $content{$fname}: $!\n";
        $errors++;
        next;
    };
    my @lines = <$fh>;
    close($fh);
    chomp(@lines);

    # Извлечение IP из метаданных
    my $ip;
    foreach my $line (@lines) {
        if ($line =~ /\%META\:FIELD\{name="DeviceIP"/) {
            if ($line =~ /value="([0-9]{1,3}(?:\.[0-9]{1,3}){3})"/) {
                $ip = $1;
                last;
            }
        }
    }
    print "IP not found!\n" and next unless $ip && is_valid_ipv4($ip);

    print "$ip \n";

    # Получение записи из user_auth по IP через API
    my $auth_response = api_call($ua, 'GET', $api_request_url. "&get=user_auth&ip=" . uri_escape($ip));
    if ($auth_response->{error} || !$auth_response->{data} || !$auth_response->{data}->{wikiname}) {
	print "Record for ip: $ip not found or WikiName is empty! Err: $auth_response->{error}\n";
	next;
	}

    my $auth = $auth_response->{data};

    # Пропускаем шлюзы
    next if ($auth->{wikiname} =~ /^Gateway/);

    print "Found: $auth->{ip} $auth->{mac} ";

    my ($device_name, $device_port);
    my $error_msg;

    # Определение типа устройства и получение родительских данных
    eval {
        if ($auth->{wikiname} =~ /^(Switch|Router)/) {
            # Для коммутаторов/маршрутизаторов: получаем данные через цепочку устройств
	    my $auth_list_response = api_call($ua, 'GET', $api_request_url. "&get=table_list&table=user_auth&filter=" . uri_escape(encode_json({ user_id => $auth->{user_id} })));
	    die "empty user ip list" unless $auth_list_response->{data} && ref($auth_list_response->{data}) eq 'ARRAY' && @{$auth_list_response->{data}} > 0;
	    my $IPS=();
	    my $device;
	    foreach my $auth_row (@{$auth_list_response->{data}}) {
		my $dev_ip = $auth_row->{ip};
                my $device_response = api_call($ua, 'GET', $api_request_url. "&get=table_record&table=devices&filter=" . uri_escape(encode_json({ ip => $dev_ip })));
	        next if (exists $device_response->{error} || !$device_response->{data});
    	        $device = $device_response->{data};
		last;
	    }
	    die "this device not found!" unless $device;

            # Получаем аплинк-порт
            my $uplink_ports_response = api_call($ua, 'GET', $api_request_url . "&get=table_record&table=device_ports&filter=" . uri_escape(encode_json({ device_id => $device->{id}, uplink => 1 })));
            die "Unknown connection" unless $uplink_ports_response->{data};
            my $parent_connect = $uplink_ports_response->{data};
            
            # Получаем целевой порт
            my $parent_port_response = api_call($ua, 'GET', $api_request_url . "&get=table_record&table=device_ports&id=" . $parent_connect->{id});
            die "Unknown port connection" unless $parent_port_response->{data};
            my $parent_port = $parent_port_response->{data};
            
            # Получаем родительское устройство
            my $device_parent_response = api_call($ua, 'GET', $api_request_url . "&get=table_record&table=devices&id=" . $parent_port->{device_id});
            die "Unknown parent device" unless $device_parent_response->{data};
            my $device_parent = $device_parent_response->{data};
            
            # Получаем авторизацию родительского устройства
            my $auth_parent_response = api_call($ua, 'GET', $api_request_url . "&get=user_auth&ip=" . uri_escape($device_parent->{ip}));
            die "Unknown auth for device" unless $auth_parent_response->{data} && $auth_parent_response->{data}->{wikiname};
            my $auth_parent = $auth_parent_response->{data};
            
            $device_name  = $auth_parent->{wikiname};
            $device_port  = $parent_port->{port};
        }
        else {
            # Для других устройств: получаем данные через соединения
            my $conn_response = api_call($ua, 'GET', $api_request_url . "&get=table_record&table=connections&filter=" . uri_escape(encode_json({ auth_id => $auth->{id} })));
            die "Unknown connection" unless $conn_response->{data};
            my $conn = $conn_response->{data};
            
            # Получаем порт устройства
            my $device_port_rec_response = api_call($ua, 'GET', $api_request_url . "&get=table_record&table=device_ports&id=" . $conn->{port_id});
            die "Unknown device port" unless $device_port_rec_response->{data};
            my $device_port_rec = $device_port_rec_response->{data};
            
            # Получаем устройство
            my $device_response = api_call($ua, 'GET', $api_request_url . "&get=table_record&table=devices&id=" . $device_port_rec->{device_id});
            die "Unknown device" unless $device_response->{data} && $device_response->{data}->{user_id};
            my $device = $device_response->{data};
            
            # Получаем авторизацию устройства по user_id и IP
            my $device_auth_list_response = api_call($ua, 'GET', $api_request_url . "&get=table_record&table=user_auth&filter=" . uri_escape(encode_json({ user_id => $device->{user_id}, ip => $device->{ip} })));
            die "Unknown device auth" unless $device_auth_list_response->{data};
            my $device_auth = $device_auth_list_response->{data};
            die "Device auth has no wikiname" unless $device_auth->{wikiname};

            $device_name = $device_auth->{wikiname};
            $device_port = $device_port_rec->{port};
        }
    };

    if ($@) {
        $error_msg = $@;
        chomp($error_msg);
        print "Error: $error_msg\n";
        next;
    }

    # Подготовка обновленного содержимого файла
    my @wiki_dev;
    my %empty_fields = (parent => 1, parent_port => 1, mac => 1);

    foreach my $line (@lines) {
        if ($line =~ /\%META\:FIELD\{name="Parent"/) {
            $empty_fields{parent} = 0;
            if ($device_name) {
                push(@wiki_dev, '%META:FIELD{name="Parent" title="Parent" value="' . $device_name . '"}%');
                next;
            }
        }
        elsif ($line =~ /\%META\:FIELD\{name="ParentPort"/) {
            $empty_fields{parent_port} = 0;
            if ($device_port) {
                push(@wiki_dev, '%META:FIELD{name="ParentPort" title="Parent Port" value="' . $device_port . '"}%');
                next;
            }
        }
        elsif ($line =~ /\%META\:FIELD\{name="Mac"/) {
            $empty_fields{mac} = 0;
            if ($auth->{mac}) {
                push(@wiki_dev, '%META:FIELD{name="Mac" title="Mac" value="' . $auth->{mac} . '"}%');
                next;
            }
        }
        push(@wiki_dev, $line);
    }

    # Добавление отсутствующих полей
    if ($empty_fields{parent} && $device_name) {
        push(@wiki_dev, '%META:FIELD{name="Parent" title="Parent" value="' . $device_name . '"}%');
    }
    if ($empty_fields{parent_port} && $device_port) {
        push(@wiki_dev, '%META:FIELD{name="ParentPort" title="Parent Port" value="' . $device_port . '"}%');
    }
    if ($empty_fields{mac} && $auth->{mac}) {
        push(@wiki_dev, '%META:FIELD{name="Mac" title="Mac" value="' . $auth->{mac} . '"}%');
    }

    $device_name  ||= 'None';
    $device_port  ||= 'None';
    print "at $device_name $device_port\n";

    # Запись обновленного файла
    open(my $out_fh, '>:encoding(UTF-8)', $content{$fname}) or do {
        warn "Ошибка записи файла $content{$fname}: $!\n";
        $errors++;
        next;
    };
    print $out_fh $_ . "\n" for @wiki_dev;
    close($out_fh);

    $processed++;
}

print "\n=== ИТОГИ ===\n";
print "Обработано файлов: $processed\n";
print "Ошибок: $errors\n";
print "Синхронизация завершена.\n";

exit 0;

sub api_call {
    my ($ua, $method, $url) = @_;
    my $req = HTTP::Request->new($method => $url);
    my $res = $ua->request($req);
    my $result = {};
    if (!$res->is_success) {
        $result->{error} = $res->status_line;
        return $result;
    }
    eval {
        $result->{data} = decode_json($res->decoded_content);
    };
    if ($@) {
        $result->{error} = "JSON parse error: $@";
    }
    return $result;
}

sub is_valid_ipv4 {
    my ($ip) = @_;
    return $ip =~ /^([0-9]{1,3}\.){3}[0-9]{1,3}$/ &&
           !grep { $_ > 255 } split(/\./, $ip);
}

sub wanted {
    my $filename = $File::Find::name;
    my $dev_name = basename($filename);
    if ($dev_name =~ /\.txt$/ && $dev_name =~ /^(Device|Switch|Ups|Sensor|Gateway|Router|Server|Bras)/) {
        $dev_name =~ s/\.txt$//;
        $content{$dev_name} = $filename;
    }
    return;
}
