using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Nexus.Contracts.Devices;
using NexusIdentity.Api.Data;
using NexusIdentity.Api.Models;
using NexusIdentity.Api.Services;
using HealthStatus = Nexus.Contracts.Devices.HealthStatus;

namespace NexusIdentity.Api.Controllers;

[ApiController]
[Route("api/devices")]
public sealed class DevicesController : ControllerBase
{
    private readonly IdentityDbContext _db;
    private readonly DevicePresenceService _presence;

    public DevicesController(IdentityDbContext db, DevicePresenceService presence)
    {
        _db = db;
        _presence = presence;
    }

    [HttpPost("register")]
    public async Task<IActionResult> Register([FromBody] DeviceRegistrationRequest request, CancellationToken ct)
    {
        if (string.IsNullOrWhiteSpace(request.DeviceId))
        {
            return BadRequest(new { error = "DeviceId is required." });
        }

        var now = DateTime.UtcNow;
        var device = await _db.Devices
            .Include(d => d.Trust)
            .FirstOrDefaultAsync(d => d.DeviceId == request.DeviceId, ct);

        if (device is null)
        {
            device = new DeviceEntity
            {
                DeviceId = request.DeviceId,
                ComputerName = request.ComputerName,
                FriendlyName = request.ComputerName,
                Owner = string.Empty,
                Os = request.Os,
                AgentVersion = request.AgentVersion,
                ShellVersion = request.Version,
                EnrollmentStatus = "Enrolled",
                RegisteredAtUtc = now,
                LastSeenUtc = now,
                Online = true,
                LastHealth = "Healthy",
            };
            _db.Devices.Add(device);
            _db.DeviceTrust.Add(new DeviceTrustEntity
            {
                DeviceId = request.DeviceId,
                Status = "Pending",
                EvaluatedAtUtc = now,
            });
        }
        else
        {
            device.ComputerName = request.ComputerName;
            device.Os = request.Os;
            device.AgentVersion = request.AgentVersion;
            device.ShellVersion = request.Version;
            device.LastSeenUtc = now;
            device.Online = true;
        }

        await _db.SaveChangesAsync(ct);

        return Ok(new
        {
            deviceId = request.DeviceId,
            registered = true,
            registeredAtUtc = device.RegisteredAtUtc,
        });
    }

    [HttpPost("heartbeat")]
    public async Task<IActionResult> Heartbeat([FromBody] DeviceHeartbeatRequest request, CancellationToken ct)
    {
        if (string.IsNullOrWhiteSpace(request.DeviceId))
        {
            return BadRequest(new { error = "DeviceId is required." });
        }

        var now = DateTime.UtcNow;
        var receivedAt = request.Timestamp?.UtcDateTime ?? now;
        var healthStatus = DevicePresenceService.ParseHealthStatus(request.Health);

        var device = await _db.Devices
            .Include(d => d.Organization)
            .Include(d => d.Presence)
            .FirstOrDefaultAsync(d => d.DeviceId == request.DeviceId, ct);

        if (device is not null)
        {
            device.LastSeenUtc = receivedAt;
            device.LastHealth = DevicePresenceService.ToHealthLabel(healthStatus);
            device.Online = request.Online;
            device.AgentVersion = request.AgentVersion;
            device.ShellVersion = request.ShellVersion;
            if (!string.IsNullOrWhiteSpace(request.User))
            {
                device.Owner = request.User;
            }

            if (device.Presence is null)
            {
                device.Presence = new DevicePresenceEntity { DeviceId = device.DeviceId };
                _db.DevicePresence.Add(device.Presence);
            }

            device.Presence.Online = request.Online;
            device.Presence.HealthStatus = healthStatus;
            device.Presence.LastSeenUtc = receivedAt;
            device.Presence.LastHeartbeatUtc = receivedAt;
            device.Presence.UserName = request.User ?? device.Owner;
            device.Presence.OrganizationName = request.Organization ?? device.Organization?.Name ?? string.Empty;
            device.Presence.AgentVersion = request.AgentVersion;
            device.Presence.ShellVersion = request.ShellVersion;
            device.Presence.UpdatedAtUtc = now;

            _db.DeviceHeartbeats.Add(new DeviceHeartbeatEntity
            {
                Id = Guid.NewGuid(),
                DeviceId = device.DeviceId,
                ReceivedAtUtc = now,
                Online = request.Online,
                HealthStatus = healthStatus,
                UserName = request.User ?? device.Owner,
                OrganizationName = request.Organization ?? device.Organization?.Name ?? string.Empty,
                AgentVersion = request.AgentVersion,
                ShellVersion = request.ShellVersion,
            });

            await _db.SaveChangesAsync(ct);
        }

        return Ok(new { acknowledged = true, receivedAtUtc = DateTimeOffset.UtcNow });
    }

    [HttpGet("status")]
    public async Task<IActionResult> GetStatus([FromQuery] string? organizationId, CancellationToken ct)
    {
        var now = DateTime.UtcNow;
        var query = _db.Devices.AsNoTracking().AsQueryable();
        if (!string.IsNullOrWhiteSpace(organizationId))
        {
            query = query.Where(d => d.OrganizationId == organizationId);
        }

        var devices = await query
            .Include(d => d.Organization)
            .Include(d => d.Presence)
            .Include(d => d.Trust)
            .OrderByDescending(d => d.LastSeenUtc)
            .ToListAsync(ct);

        var rows = devices.Select(d => MapDeviceStatus(d, now)).ToList();
        return Ok(new DeviceStatusResponse
        {
            GeneratedAtUtc = now,
            OfflineThresholdSeconds = _presence.OfflineThresholdSeconds,
            OnlineCount = rows.Count(r => r.Online),
            OfflineCount = rows.Count(r => !r.Online),
            WarningCount = rows.Count(r => r.Online && r.HealthStatus == HealthStatus.Warning.ToString()),
            CriticalCount = rows.Count(r => r.Online && r.HealthStatus == HealthStatus.Critical.ToString()),
            Devices = rows,
        });
    }

    [HttpGet("online")]
    public async Task<IActionResult> GetOnline([FromQuery] string? organizationId, CancellationToken ct)
    {
        var status = await GetStatus(organizationId, ct);
        if (status is not OkObjectResult ok || ok.Value is not DeviceStatusResponse response)
        {
            return status;
        }

        return Ok(new
        {
            response.GeneratedAtUtc,
            response.OfflineThresholdSeconds,
            count = response.OnlineCount,
            devices = response.Devices.Where(d => d.Online).ToList(),
        });
    }

    [HttpGet]
    public async Task<IActionResult> List(CancellationToken ct)
    {
        var now = DateTime.UtcNow;
        var devices = await _db.Devices
            .AsNoTracking()
            .Include(d => d.Trust)
            .Include(d => d.Presence)
            .OrderByDescending(d => d.LastSeenUtc)
            .Select(d => new
            {
                d.DeviceId,
                d.ComputerName,
                os = d.Os,
                d.AgentVersion,
                shellVersion = d.ShellVersion,
                d.OrganizationId,
                d.Owner,
                d.FriendlyName,
                registeredAtUtc = d.RegisteredAtUtc,
                d.LastSeenUtc,
                d.LastHealth,
                online = d.Presence != null ? d.Presence.Online : d.Online,
                trustStatus = d.Trust != null ? d.Trust.Status : "Unknown",
                enrollmentStatus = d.EnrollmentStatus,
            })
            .ToListAsync(ct);

        return Ok(devices);
    }

    [HttpGet("{deviceId}")]
    public async Task<IActionResult> Get(string deviceId, CancellationToken ct)
    {
        var device = await _db.Devices
            .AsNoTracking()
            .Include(d => d.Trust)
            .Include(d => d.Organization)
            .Include(d => d.Presence)
            .FirstOrDefaultAsync(d => d.DeviceId == deviceId, ct);

        if (device is null)
        {
            return NotFound();
        }

        var now = DateTime.UtcNow;
        var status = MapDeviceStatus(device, now);

        return Ok(new
        {
            device.DeviceId,
            device.ComputerName,
            os = device.Os,
            device.AgentVersion,
            shellVersion = device.ShellVersion,
            device.OrganizationId,
            organizationName = device.Organization?.Name,
            device.Owner,
            device.FriendlyName,
            registeredAtUtc = device.RegisteredAtUtc,
            device.LastSeenUtc,
            device.LastHealth,
            status.Online,
            status.HealthStatus,
            trustStatus = device.Trust?.Status ?? "Unknown",
            enrollmentStatus = device.EnrollmentStatus,
            status.LastHeartbeatUtc,
        });
    }

    private DeviceStatusRow MapDeviceStatus(DeviceEntity device, DateTime utcNow)
    {
        var lastSeen = device.Presence?.LastSeenUtc ?? device.LastSeenUtc;
        var online = _presence.IsOnline(lastSeen, utcNow) && (device.Presence?.Online ?? device.Online);
        var health = online
            ? DevicePresenceService.ToHealthLabel(device.Presence?.HealthStatus ?? DevicePresenceService.ParseHealthStatus(device.LastHealth))
            : HealthStatus.Offline.ToString();

        return new DeviceStatusRow
        {
            DeviceId = device.DeviceId,
            FriendlyName = device.FriendlyName,
            ComputerName = device.ComputerName,
            Owner = device.Presence?.UserName ?? device.Owner,
            OrganizationId = device.OrganizationId,
            OrganizationName = device.Presence?.OrganizationName ?? device.Organization?.Name ?? string.Empty,
            Online = online,
            HealthStatus = health,
            AgentVersion = device.Presence?.AgentVersion ?? device.AgentVersion,
            ShellVersion = device.Presence?.ShellVersion ?? device.ShellVersion,
            LastSeenUtc = lastSeen,
            LastHeartbeatUtc = device.Presence?.LastHeartbeatUtc ?? lastSeen,
            TrustStatus = device.Trust?.Status ?? "Unknown",
            EnrollmentStatus = device.EnrollmentStatus,
        };
    }
}

public sealed class DeviceStatusResponse
{
    public DateTime GeneratedAtUtc { get; init; }
    public int OfflineThresholdSeconds { get; init; }
    public int OnlineCount { get; init; }
    public int OfflineCount { get; init; }
    public int WarningCount { get; init; }
    public int CriticalCount { get; init; }
    public IReadOnlyList<DeviceStatusRow> Devices { get; init; } = [];
}

public sealed class DeviceStatusRow
{
    public required string DeviceId { get; init; }
    public required string FriendlyName { get; init; }
    public required string ComputerName { get; init; }
    public required string Owner { get; init; }
    public string? OrganizationId { get; init; }
    public required string OrganizationName { get; init; }
    public bool Online { get; init; }
    public required string HealthStatus { get; init; }
    public required string AgentVersion { get; init; }
    public required string ShellVersion { get; init; }
    public DateTime? LastSeenUtc { get; init; }
    public DateTime? LastHeartbeatUtc { get; init; }
    public required string TrustStatus { get; init; }
    public required string EnrollmentStatus { get; init; }
}
