using System.Net;
using adas_core.Application.Exceptions;
using adas_core.Application.Services.Interfaces;
using adas_core.Authentication.Attributes;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Utils;
using adas_core.Logging;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using MongoDB.Bson;

namespace adas_core.Controllers;

[ApiController]
[Route("Relay")]
public class RelayController : ControllerBase
{
    // TODO: ver donde ses usa este controlador para version 3 del modelo

    private string? _url = string.Empty;
    private readonly IPointOfCareService _pointOfCareService;
    private readonly IRelayService _relayService;
    private readonly ILogger<RelayController> _logger;

    public RelayController(
        IOptions<RecordingSettings> recordingSettings,
        IRelayService relayService,
        ILogger<RelayController> logger, IPointOfCareService pointOfCareService)
    {
        LogExecutionContext.TrySetTraceIdentifier(Guid.NewGuid().ToString(), true);

        _relayService = relayService;
        _logger = logger;
        _pointOfCareService = pointOfCareService;
    }


    /// <summary>
    /// Retrieves the status of point-of-care sections by querying the relay status for each configured box and its associated relays. Returns a 404 Not Found when no configurations are available, or a list containing the unit, bed, and status for each relay when configurations exist.
    /// </summary>
    /// <returns>An <see cref="IActionResult"/> that is either a 404 Not Found when no configurations are found, or an HTTP 200 OK response containing a collection of objects with the unit name, bed, and relay status.</returns>
    [HttpGet]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [Route("status")]
    public async Task<IActionResult> CheckSectionStatus()
    {
        var section = await _pointOfCareService.GetAllConfigs();
        if (section.IsNullOrEmpty()) return NotFound();
        var boxes = section.Where(b => b.Configuration != null && !b.Configuration.RelayList.IsNullOrEmpty()).ToList();
        var dict = new List<object>();
        foreach (var box in boxes)
        {
            foreach (var relay in box.Configuration!.RelayList!)
            {
                var status = await _relayService.CheckRelayStatus(relay);
                dict.Add(new { Unit = box.Unit?.Name ?? box.UnitId.ToString(), bed = box.Bed, status });
            }
        }

        return Ok(dict);
    }


    /// <summary>
    /// Handles a power-on request for a Point of Care (PoC) device by delegating to the shared power action pipeline and turning on the configured relay for the specified unit, point of care, and bed.
    /// </summary>
    /// <param name="action">The request payload containing the identifiers of the unit, point of care, and bed to power on.</param>
    /// <returns>An <see cref="IActionResult"/> representing the result of the power-on operation.</returns>
    [HttpPost]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [Route("powerOn")]
    public async Task<IActionResult> PowerOnPoC([FromBody] PowerActionRequest action)
    {
        return await PowerAction(action.UnitId, action.PointOfCareId, action.Bed, async (relayConfig) =>
        {
            await _relayService.PowerOn(relayConfig);
        });
    }

    /// <summary>
    /// Powers off the relay configuration for the specified unit, point of care, and bed by delegating the operation to the relay service.
    /// </summary>
    /// <param name="action">The power action request containing the unit identifier, point of care identifier, and bed information.</param>
    /// <returns>An <see cref="IActionResult"/> representing the result of the power off operation.</returns>
    [HttpPost]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [Route("powerOff")]
    public async Task<IActionResult> PowerOffPoC([FromBody] PowerActionRequest action)
    {
        return await PowerAction(action.UnitId, action.PointOfCareId, action.Bed, async (relayConfig) =>
        {
            await _relayService.PowerOff(relayConfig);
        });
    }

    /// <summary>
    /// Executes the specified power action on every relay in the configuration of a point of care device, after validating the device identifier and ensuring a relay list is available.
    /// </summary>
    /// <param name="unitId">The identifier of the unit associated with the point of care.</param>
    /// <param name="pointOfCareId">The string identifier of the point of care, parsed as an <see cref="ObjectId"/>.</param>
    /// <param name="bed">The bed identifier associated with the point of care.</param>
    /// <param name="action">The asynchronous operation to invoke against each relay found in the configuration.</param>
    /// <returns>An <see cref="IActionResult"/> that returns <c>Ok</c> when the action runs on the relays, <c>Conflict</c> if <paramref name="pointOfCareId"/> cannot be parsed as an <see cref="ObjectId"/>, or <c>NotFound</c> if the box or its relay configuration is missing.</returns>
    private async Task<IActionResult> PowerAction(string unitId, string pointOfCareId, string bed, Func<Relay, Task> action)
    {
        if (!ObjectId.TryParse(pointOfCareId, out var poc)) return Conflict("Cannot parse to OBjectId");

        var box = await _pointOfCareService.FindByIdAllConfig(poc);
        if (box == null) return NotFound("No box found");

        if (box.Configuration == null || box.Configuration.RelayList.IsNullOrEmpty()) return NotFound("No relay configuration found");

        foreach (var relay in box.Configuration.RelayList!)
        {
            await action(relay);
        }

        return Ok();
    }



    /// <summary>
    /// Checks the status of a relay device by building a relay configuration from the supplied parameters and delegating the status check to the relay service.
    /// </summary>
    /// <param name="id">The relay number identifier used to select the specific relay.</param>
    /// <param name="driver">The driver type used to communicate with the relay.</param>
    /// <param name="host">The host IP address of the relay device.</param>
    /// <param name="port">The network port used to connect to the relay device.</param>
    /// <param name="relayName">The name assigned to the relay configuration.</param>
    /// <param name="total">The total number of relays in the configuration.</param>
    /// <param name="username">The username used for relay authentication.</param>
    /// <param name="password">The password used for relay authentication.</param>
    /// <returns>An <see cref="IActionResult"/> containing the relay status result returned by the relay service.</returns>
    [HttpPost]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthAdmin)]
    [Route("{id}/status")]
    public IActionResult CheckRelayStatus(int id, string driver, string host, int port, string relayName, int total, string username, string password)
    {
        Relay relayConfig = new()
        {
            Driver = driver,
            Ip = host,
            Port = port,
            RelayNumber = id,
            RelayName = relayName,
            Total = total,
            Username = username,
            Password = password
        };
        var strConfig = relayConfig.ToString() ?? "null";
        _logger.LogDebug("Config:{strConfig}", strConfig);

        var result = _relayService.CheckRelayStatus(relayConfig);

        var resultStr = result.ToString() ?? "null";
        _logger.LogDebug("Relay Status {resultStr}", resultStr);

        return Ok(result);
    }

    /// <summary>
    /// Powers on the relay identified by the specified id using the provided connection configuration.
    /// Returns HTTP 200 with a success indicator when the relay is powered on, or HTTP 500 if the relay service throws an exception.
    /// </summary>
    /// <param name="id">The identifier of the relay to power on.</param>
    /// <param name="driver">The relay driver to use for the connection.</param>
    /// <param name="host">The host address of the relay device.</param>
    /// <param name="port">The port number used to connect to the relay device.</param>
    /// <param name="relayName">The name assigned to the relay.</param>
    /// <param name="total">The total number of relays managed by the device.</param>
    /// <param name="username">The username used to authenticate with the relay device.</param>
    /// <param name="password">The optional password used to authenticate with the relay device.</param>
    /// <returns>An <see cref="IActionResult"/> containing HTTP 200 with a success value on success, or HTTP 500 if the relay service fails.</returns>
    [HttpPost]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [Route("{id}/power/on")]
    public IActionResult PowerOn(int id, string driver, string host, int port, string relayName, int total, string username, string? password)
    {
        Relay relayConfig = new()
        {
            Driver = driver,
            Ip = host,
            Port = port,
            RelayNumber = id,
            RelayName = relayName,
            Total = total,
            Username = username,
            Password = password
        };

        var strConfig = relayConfig.ToString() ?? "null";
        _logger.LogDebug("Config: {strConfig}", strConfig);


        try
        {
            _relayService.PowerOn(relayConfig);
        }
        catch (Exception)
        {
            return StatusCode(int.Parse(HttpStatusCode.InternalServerError.ToString()));

        }


        return Ok(true);
    }

    /// <summary>
    /// Powers off a relay identified by the given id using the provided relay configuration parameters.
    /// On failure to power off the relay, returns an Internal Server Error status; otherwise returns OK with a false result.
    /// </summary>
    /// <param name="id">The identifier of the relay to power off.</param>
    /// <param name="driver">The relay driver type to use for the operation.</param>
    /// <param name="host">The host address of the relay device.</param>
    /// <param name="port">The port number used to communicate with the relay device.</param>
    /// <param name="relayName">The name assigned to the relay.</param>
    /// <param name="total">The total number of relays managed by the device.</param>
    /// <param name="username">The username used for relay authentication.</param>
    /// <param name="password">The optional password used for relay authentication.</param>
    /// <returns>An <see cref="IActionResult"/> indicating the outcome: Ok with a false value on success, or an Internal Server Error status if the power off operation fails.</returns>
    [HttpPost]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [Route("{id}/power/off")]
    public IActionResult PowerOff(int id, string driver, string host, int port, string relayName, int total, string username, string? password)
    {
        Relay relayConfig = new()
        {
            Driver = driver,
            Ip = host,
            Port = port,
            RelayNumber = id,
            RelayName = relayName,
            Total = total,
            Username = username,
            Password = password
        };

        var strConfig = relayConfig.ToString() ?? "null";
        _logger.LogDebug("Config: {strConfig}", strConfig);

        try
        {
            _relayService.PowerOff(relayConfig);
        }
        catch (Exception)
        {
            return StatusCode(int.Parse(HttpStatusCode.InternalServerError.ToString()));
        }
        return Ok(false);
    }


    /// <summary>
    /// Manually switches a relay for a specific point of connection (POC) to a given status and type.
    /// Validates the input parameters and returns a bad request if the status, type, or POC identifier cannot be parsed;
    /// returns an internal server error if an unexpected exception occurs, otherwise returns a successful response.
    /// </summary>
    /// <param name="status">The desired relay status as a string, which is parsed into <see cref="RelayEnum.Status"/>.</param>
    /// <param name="pocId">The identifier of the point of connection, which must be a valid ObjectId string.</param>
    /// <param name="type">The relay type as a string, which is parsed into <see cref="RelayEnum.Type"/>.</param>
    /// <returns>An <see cref="IActionResult"/> indicating the outcome: <see cref="BadRequestResult"/> for invalid input, <see cref="StatusCodeResult"/> for internal server errors, or <see cref="OkObjectResult"/> with <c>false</c> on success.</returns>
    [HttpPost]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [Route("manual/{status}")]
    public IActionResult ManualSwitch(string status, string pocId, string type)
    {
        try
        {
            if (!Enum.TryParse(status, out RelayEnum.Status relayStatus))
                return BadRequest();
            if (!Enum.TryParse(type, out RelayEnum.Type relayType))
                return BadRequest();
            if (!ObjectId.TryParse(pocId, out var poc))
                return BadRequest();

            _ = _relayService.SetManualRelay(relayStatus, poc, relayType);
        }
        catch (Exception)
        {
            return StatusCode(int.Parse(HttpStatusCode.InternalServerError.ToString()));
        }
        return Ok(false);
    }
    /// <summary>
    /// Retrieves a paginated list of relays based on the provided pagination filter.
    /// </summary>
    /// <param name="request">The pagination filter containing the paging parameters.</param>
    /// <param name="withPoCs">Flag indicating whether to include related point of contact data in the response.</param>
    /// <returns>An <see cref="IActionResult"/> containing the paginated relays.</returns>
    /// <exception cref="BadRequestException">Thrown when the <paramref name="request"/> is null.</exception>
    [HttpPost("paginated")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    public async Task<IActionResult> GetPaginatedUnits([FromBody] PaginationFilter request, bool withPoCs)
    {
        if (request == null) throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestMissingParameters);
        var relays = await _relayService.GetPaginatedRelays(request);
        return Ok(relays);
    }
    /// <summary>
    /// Creates a new relay based on the provided request payload.
    /// Throws a bad request exception if the request body is missing required parameters.
    /// </summary>
    /// <param name="request">The relay data to create, supplied in the request body.</param>
    /// <returns>An <see cref="IActionResult"/> containing the created relay.</returns>
    /// <exception cref="BadRequestException">Thrown when the <paramref name="request"/> is null.</exception>
    [HttpPost]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    public async Task<IActionResult> CreateRelay([FromBody] Relay? request)
    {
        if (request == null) throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestMissingParameters);
        var relays = await _relayService.InsertRelay(request);
        return Ok(relays);
    }
    /// <summary>
    /// Updates an existing relay identified by the specified id with the provided relay data.
    /// </summary>
    /// <param name="relay">The relay data to apply to the existing record.</param>
    /// <param name="id">The identifier of the relay to update, expected to be a valid ObjectId.</param>
    /// <returns>An <see cref="IActionResult"/> containing the updated relay when the operation succeeds.</returns>
    /// <exception cref="BadRequestException">Thrown when the provided <paramref name="id"/> cannot be parsed as a valid ObjectId.</exception>
    [HttpPut("{id}")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    public async Task<IActionResult> UpdateRelay([FromBody] Relay relay, string id)
    {
        if (!ObjectId.TryParse(id, out var idParsed)) throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestMissingParameters);
        var relayUpdated = await _relayService.UpdateRelayById(idParsed, relay);
        return Ok(relayUpdated);
    }

}

/// <summary>
/// Represents a request to perform a power-related action, encapsulating the data required to execute the operation.
/// </summary>
public class PowerActionRequest
{
    public string UnitId { get; set; } = null!;
    public string PointOfCareId { get; set; } = null!;
    public string Bed { get; set; } = null!;
}