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

namespace Watcher.Controllers;

public class RegistrationDto
{
    // 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; } // GB

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

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

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

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

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

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

    // Network
    public double NET_In { get; set; } // Bit

    public double NET_Out { get; set; } // Bit
}


[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 dem sich neue Agents registrieren
    [HttpPost("register-agent-by-id")]
    public async Task<IActionResult> Register([FromBody] RegistrationDto 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.");

    }

    [HttpGet("server-id-by-ip")]
    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)
        {
            var NewMetric = new Metric
            {
                Timestamp = DateTime.UtcNow,
                ServerId = dto.ServerId,
                CPU_Load = dto.CPU_Load,
                CPU_Temp = dto.CPU_Temp,
                GPU_Load = dto.GPU_Load,
                GPU_Temp = dto.GPU_Temp,
                GPU_Vram_Size = dto.GPU_Vram_Size,
                GPU_Vram_Usage = dto.GPU_Vram_Usage,
                RAM_Load = dto.RAM_Load,
                RAM_Size = dto.RAM_Size,
                DISK_Size = dto.RAM_Size,
                DISK_Usage = dto.DISK_Usage,
                DISK_Temp = dto.DISK_Temp,
                NET_In = dto.NET_In,
                NET_Out = dto.NET_Out
            };
            try
            {
                _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.");

    }


    // Durchschnittliche Werte Berechnen
    [HttpGet]
    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();
    }
}