using System.Text.Json;
using Grpc.Core;
using Microsoft.EntityFrameworkCore;
using NexusRMM.Core.Models;
using NexusRMM.Infrastructure.Data;
using NexusRMM.Protos;
using AgentModel = NexusRMM.Core.Models.Agent;
using CoreTaskStatus = NexusRMM.Core.Models.TaskStatus;

namespace NexusRMM.Api.GrpcServices;

public class AgentGrpcService : AgentService.AgentServiceBase
{
    private readonly RmmDbContext _db;
    private readonly ILogger<AgentGrpcService> _logger;

    public AgentGrpcService(RmmDbContext db, ILogger<AgentGrpcService> logger)
    {
        _db = db;
        _logger = logger;
    }

    public override async Task<EnrollResponse> Enroll(EnrollRequest request, ServerCallContext context)
    {
        var agent = new AgentModel
        {
            Id = Guid.NewGuid(),
            Hostname = request.Hostname,
            OsType = request.OsType.ToLower() == "windows" ? OsType.Windows : OsType.Linux,
            OsVersion = request.OsVersion,
            IpAddress = request.IpAddress,
            MacAddress = request.MacAddress,
            AgentVersion = request.AgentVersion,
            Status = AgentStatus.Online,
            LastSeen = DateTime.UtcNow,
            EnrolledAt = DateTime.UtcNow
        };

        _db.Agents.Add(agent);
        await _db.SaveChangesAsync();
        _logger.LogInformation("Agent enrolled: {AgentId} ({Hostname})", agent.Id, agent.Hostname);

        return new EnrollResponse
        {
            AgentId = agent.Id.ToString(),
            HeartbeatInterval = 60
        };
    }

    public override async Task<HeartbeatResponse> Heartbeat(HeartbeatRequest request, ServerCallContext context)
    {
        var agentId = Guid.Parse(request.AgentId);
        var agent = await _db.Agents.FindAsync(agentId)
            ?? throw new RpcException(new Status(StatusCode.NotFound, "Agent not found"));

        agent.LastSeen = DateTime.UtcNow;
        agent.Status = AgentStatus.Online;

        _db.AgentMetrics.Add(new AgentMetric
        {
            AgentId = agentId,
            Timestamp = DateTime.UtcNow,
            Metrics = JsonSerializer.SerializeToElement(request.Metrics)
        });

        var pendingTasks = await _db.Tasks
            .Where(t => t.AgentId == agentId && t.Status == CoreTaskStatus.Pending)
            .OrderBy(t => t.CreatedAt)
            .Take(10)
            .ToListAsync();

        var response = new HeartbeatResponse();
        foreach (var task in pendingTasks)
        {
            task.Status = CoreTaskStatus.InProgress;
            response.PendingCommands.Add(new AgentCommand
            {
                CommandId = task.Id.ToString(),
                Type = MapTaskType(task.Type),
                Payload = task.Payload?.GetRawText() ?? "{}"
            });
        }

        await _db.SaveChangesAsync();
        return response;
    }

    public override async Task<CommandResultResponse> ReportCommandResult(CommandResult request, ServerCallContext context)
    {
        var taskId = Guid.Parse(request.CommandId);
        var task = await _db.Tasks.FindAsync(taskId)
            ?? throw new RpcException(new Status(StatusCode.NotFound, "Task not found"));

        task.Status = request.Success ? CoreTaskStatus.Completed : CoreTaskStatus.Failed;
        task.CompletedAt = DateTime.UtcNow;
        task.Result = JsonSerializer.SerializeToElement(new
        {
            request.ExitCode,
            request.Stdout,
            request.Stderr,
            request.Success
        });

        await _db.SaveChangesAsync();
        return new CommandResultResponse();
    }

    private static CommandType MapTaskType(NexusRMM.Core.Models.TaskType type) => type switch
    {
        NexusRMM.Core.Models.TaskType.Shell => CommandType.Shell,
        NexusRMM.Core.Models.TaskType.InstallSoftware => CommandType.InstallSoftware,
        NexusRMM.Core.Models.TaskType.UninstallSoftware => CommandType.UninstallSoftware,
        NexusRMM.Core.Models.TaskType.UpdateAgent => CommandType.UpdateAgent,
        NexusRMM.Core.Models.TaskType.NetworkScan => CommandType.NetworkScan,
        _ => CommandType.Unspecified
    };
}
