using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Watcher.Data;
using Watcher.Models;
using Watcher.ViewModels;

namespace Watcher.Controllers;

public class HardwareDto
{
    // Server Identity
    [Required]
    public int Id { get; set; }

    [Required]
    public string? IpAddress { get; set; }

    // Hardware Info
    [Required]
    public string? CpuType { get; set; }

    [Required]
    public int CpuCores { get; set; }

    [Required]
    public string? GpuType { get; set; }

    [Required]
    public double RamSize { get; set; }

}

public class MetricDto
{
    // Server Identity
    [Required]
    public int ServerId { get; set; }

    [Required]
    public string? IpAddress { get; set; }

    // Hardware Metrics
    // CPU
    public double CPU_Load { get; set; } // %

    public double CPU_Temp { get; set; } // deg C

    // GPU
    public double GPU_Load { get; set; } // %

    public double GPU_Temp { get; set; } // deg C

    public double GPU_Vram_Size { get; set; } // Bytes

    public double GPU_Vram_Load { get; set; } // %

    // RAM
    public double RAM_Size { get; set; } // Bytes

    public double RAM_Load { get; set; } // %

    // Disks
    public double DISK_Size { get; set; } // Bytes

    public double DISK_Usage { get; set; } // Bytes

    public double DISK_Temp { get; set; } // deg C (if available)

    // Network
    public double NET_In { get; set; } // Bytes/s

    public double NET_Out { get; set; } // Bytes/s
}

public class ServiceDto
{
    public int ServerId { get; set; } // Vom Watcher-Server zugewiesene ID des Hosts
    public String? ContainerId { get; set; }
    public String? Image { get; set; }
    public String? Name { get; set; }
}

[ApiController]
[Route("[controller]")]
public class MonitoringController : Controller
{
    private readonly AppDbContext _context;

    private readonly ILogger<MonitoringController> _logger;

    public MonitoringController(AppDbContext context, ILogger<MonitoringController> logger)
    {
        _context = context;
        _logger = logger;
    }


    // Endpoint, an den der Agent seine Hardwareinformationen schickt
    [HttpPost("hardware-info")]
    public async Task<IActionResult> Register([FromBody] HardwareDto dto)
    {
        // Gültigkeit des Payloads prüfen
        if (!ModelState.IsValid)
        {
            var errors = ModelState.Values
                .SelectMany(v => v.Errors)
                .Select(e => e.ErrorMessage)
                .ToList();

            _logger.LogError("Fehlerhafter Registrierungs-Payload.");
            return BadRequest(new { error = "Ungültiger Payload", details = errors });
        }

        // Server in Datenbank finden
        var server = await _context.Servers
            .FirstOrDefaultAsync(s => s.IPAddress == dto.IpAddress);

        if (server != null)
        {
            // Serverdaten in Datenbank eintragen
            server.CpuType = dto.CpuType;
            server.CpuCores = dto.CpuCores;
            server.GpuType = dto.GpuType;
            server.RamSize = dto.RamSize;

            // Änderungen in Datenbank speichern
            await _context.SaveChangesAsync();

            // Success
            _logger.LogInformation("Agent für '{server}' erfolgreich registriert.", server.Name);
            return Ok();
        }
        _logger.LogError("Kein Server für Registrierung gefunden");
        return NotFound("No Matching Server found.");
    }

    // Endpoint, an dem sich ein Agent initial registriert
    [HttpGet("register")]
    public async Task<IActionResult> GetServerIdByIp([FromQuery] string IpAddress)
    {
        var server = await _context.Servers
            .FirstOrDefaultAsync(s => s.IPAddress == IpAddress);

        if (server == null)
            return NotFound();

        return Ok(new
        {
            id = server.Id,
            IpAddress = server.IPAddress
        });
    }


    // Enpoint, an den Agents Ihre gesammelten Daten senden
    [HttpPost("metric")]
    public async Task<IActionResult> Receive([FromBody] MetricDto dto)
    {
        // Gültigkeit des Payloads prüfen
        if (!ModelState.IsValid)
        {
            var errors = ModelState.Values
                .SelectMany(v => v.Errors)
                .Select(e => e.ErrorMessage)
                .ToList();

            _logger.LogError("Ungültiger Monitoring-Payload.");
            return BadRequest(new { error = "Ungültiger Payload", details = errors });
        }

        // Server in Datenbank finden
        var server = await _context.Servers
            .FirstOrDefaultAsync(s => s.IPAddress == dto.IpAddress);

        if (server != null)
        {
            // neues Metric-Objekt erstellen
            var NewMetric = new Metric
            {
                Timestamp = DateTime.UtcNow,
                ServerId = dto.ServerId,
                CPU_Load = sanitizeInput(dto.CPU_Load),
                CPU_Temp = sanitizeInput(dto.CPU_Temp),
                GPU_Load = sanitizeInput(dto.GPU_Load),
                GPU_Temp = sanitizeInput(dto.GPU_Temp),
                GPU_Vram_Size = calculateGigabyte(dto.GPU_Vram_Size),
                GPU_Vram_Usage = sanitizeInput(dto.GPU_Vram_Load),
                RAM_Load = sanitizeInput(dto.RAM_Load),
                RAM_Size = calculateGigabyte(dto.RAM_Size),
                DISK_Size = calculateGigabyte(dto.DISK_Size),
                DISK_Usage = calculateGigabyte(dto.DISK_Usage),
                DISK_Temp = sanitizeInput(dto.DISK_Temp),
                NET_In = calculateMegabit(dto.NET_In),
                NET_Out = calculateMegabit(dto.NET_Out)
            };
            try
            {
                // Metric Objekt in Datenbank einfügen
                _context.Metrics.Add(NewMetric);
                await _context.SaveChangesAsync();

                _logger.LogInformation("Monitoring-Daten für '{server}' empfangen", server.Name);
                return Ok();
            }
            catch
            {
                // Alert triggern

                _logger.LogError("Metric für {server} konnte nicht in Datenbank geschrieben werden.", server.Name);
                return BadRequest();
            }
        }

        _logger.LogError("Kein Server für eingegangenen Monitoring-Payload gefunden");
        return NotFound("No Matching Server found.");

    }

    // Endpoint, an dem Agents Ihre laufenden Services registrieren
    [HttpPost("service-discovery")]
    public async Task<IActionResult> ServiceDetection([FromBody] ServiceDto dto)
    {
        // Gültigkeit des Payloads prüfen
        if (!ModelState.IsValid)
        {
            var errors = ModelState.Values
                .SelectMany(v => v.Errors)
                .Select(e => e.ErrorMessage)
                .ToList();

            _logger.LogError("Ungültiger ServiceDetection-Payload.");
            return BadRequest(new { error = "Ungültiger Payload", details = errors });
        }

        // Metrics für Container, die zu ServerId gehören, löschen

        // Container, die zu ServerId gehören löschen

        // Container neu in Datenbank einlesen
        try
        {
            Container container = new Container
            {
                ServerId = dto.ServerId,
                ContainerId = dto.ContainerId,
                Image = dto.Name,
                Name = dto.Name
            };

            _logger.LogInformation(container.Name + " added for Host " + container.ServerId);

            _context.Containers.Add(container);
            await _context.SaveChangesAsync();
        }
        catch (SqliteException e)
        {
            _logger.LogError("ServiceDiscovery failed: " + e.Message);
        }

        return Ok();

    }

    // Durchschnittliche Werte Berechnen
    [HttpGet("median")]
    public async Task<IActionResult> CalculateMedian(string Metric, int HoursToMonitor, int ServerId)
    {
        // Aktuelle Zeit - X Stunden = letzter Wert, der berücksichtigt werden soll
        DateTime TimeToMonitor = DateTime.Now.AddHours(-HoursToMonitor);

        // Alle Metrics von Server "ServerId" finden, die in der festgelegten Zeitspanne liegen
        var MetricsInTimeFrame = await _context.Metrics
            .Where(e => e.Timestamp >= TimeToMonitor && e.ServerId == ServerId)
            .ToListAsync();

        // return Action (MetricsInTimeFrame)

        return NotFound();
    }

    [HttpGet("cpu-usage")]
    public async Task<IActionResult> GetCpuUsageData(int serverId)
    {
        var oneDayAgo = DateTime.UtcNow.AddDays(-1);
        var data = await _context.Metrics
                                .Where(m => m.Timestamp >= oneDayAgo && m.ServerId == serverId)
                                .OrderBy(m => m.Timestamp)
                                .Select(m => new
                                {
                                    label = m.Timestamp.ToUniversalTime().ToString("o"),
                                    data = m.CPU_Load
                                })
                                .ToListAsync();

        return Ok(data);
    }

    [HttpGet("ram-usage")]
    public async Task<IActionResult> GetRamUsageData(int serverId)
    {
        var oneDayAgo = DateTime.UtcNow.AddDays(-1);
        var data = await _context.Metrics
                                .Where(m => m.Timestamp >= oneDayAgo && m.ServerId == serverId)
                                .OrderBy(m => m.Timestamp)
                                .Select(m => new
                                {
                                    label = m.Timestamp.ToUniversalTime().ToString("o"),
                                    data = m.RAM_Load
                                })
                                .ToListAsync();

        return Ok(data);
    }

    [HttpGet("gpu-usage")]
    public async Task<IActionResult> GetGpuUsageData(int serverId)
    {
        var oneDayAgo = DateTime.UtcNow.AddDays(-1);
        var data = await _context.Metrics
                                .Where(m => m.Timestamp >= oneDayAgo && m.ServerId == serverId)
                                .OrderBy(m => m.Timestamp)
                                .Select(m => new
                                {
                                    label = m.Timestamp.ToUniversalTime().ToString("o"),
                                    data = m.GPU_Load
                                })
                                .ToListAsync();

        return Ok(data);
    }

    // Metric Input Byte zu Gigabyte umwandeln
    public static double calculateGigabyte(double metric_input)
    {
        // *10^-9 um auf Gigabyte zu kommen
        double calculatedValue = metric_input * Math.Pow(10, -9);

        // Auf 2 Nachkommastellen runden
        double calculatedValue_s = sanitizeInput(calculatedValue);

        return calculatedValue_s;
    }

    // Metric Input Byte/s zu Megabit/s umrechnen
    //TODO
    public static double calculateMegabit(double metric_input)
    {
        // *10^-9 um auf Gigabyte zu kommen
        double calculatedValue = metric_input * Math.Pow(10, -9);

        // Auf 2 Nachkommastellen runden
        double calculatedValue_s = sanitizeInput(calculatedValue);

        return calculatedValue_s;
    }

    // Degree Input auf zwei Nachkommastellen runden
    public static double sanitizeInput(double metric_input)
    {
        Math.Round(metric_input, 2);

        return metric_input;
    }

}