using Extrudex.API.DTOs.Printers;
using Extrudex.Domain.Entities;
using Extrudex.Domain.Enums;
using Extrudex.Infrastructure.Data;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace Extrudex.API.Controllers;

/// <summary>
/// Controller for managing 3D printers in the fleet.
/// Provides CRUD operations, a lightweight status endpoint, and soft-delete.
/// Real-time printer status is also available via the SignalR PrinterHub.
/// </summary>
[ApiController]
[Route("api/printers")]
public class PrintersController : ControllerBase
{
    private readonly ExtrudexDbContext _dbContext;
    private readonly ILogger<PrintersController> _logger;

    /// <summary>
    /// Initializes a new instance of the <see cref="PrintersController"/> class.
    /// </summary>
    /// <param name="dbContext">The database context for data access.</param>
    /// <param name="logger">The logger for diagnostic output.</param>
    public PrintersController(ExtrudexDbContext dbContext, ILogger<PrintersController> logger)
    {
        _dbContext = dbContext;
        _logger = logger;
    }

    /// <summary>
    /// Gets all printers, optionally filtered by active status.
    /// Results are ordered by printer name alphabetically.
    /// </summary>
    /// <param name="isActive">
    /// Optional filter: <c>true</c> for active printers only,
    /// <c>false</c> for inactive (soft-deleted) printers,
    /// omit for all printers.
    /// </param>
    /// <returns>A list of printers matching the filter criteria.</returns>
    /// <response code="200">Returns the list of printers.</response>
    [HttpGet]
    [ProducesResponseType(typeof(IEnumerable<PrinterResponse>), StatusCodes.Status200OK)]
    public async Task<ActionResult<IEnumerable<PrinterResponse>>> GetPrinters(
        [FromQuery] bool? isActive = null)
    {
        _logger.LogDebug("Getting printers, isActive filter: {IsActive}", isActive);

        var query = _dbContext.Printers.AsQueryable();

        if (isActive.HasValue)
            query = query.Where(p => p.IsActive == isActive.Value);

        var printers = await query
            .OrderBy(p => p.Name)
            .Select(p => MapToPrinterResponse(p))
            .ToListAsync();

        return Ok(printers);
    }

    /// <summary>
    /// Gets a specific printer by its unique identifier.
    /// </summary>
    /// <param name="id">The unique identifier of the printer.</param>
    /// <returns>The full printer details including connection configuration.</returns>
    /// <response code="200">Returns the printer details.</response>
    /// <response code="404">The printer was not found.</response>
    [HttpGet("{id:guid}")]
    [ProducesResponseType(typeof(PrinterResponse), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<ActionResult<PrinterResponse>> GetPrinter(Guid id)
    {
        _logger.LogDebug("Getting printer {Id}", id);

        var printer = await _dbContext.Printers.FindAsync(id);
        if (printer is null)
        {
            _logger.LogWarning("Printer {Id} not found", id);
            return NotFound(new { error = $"Printer with ID '{id}' not found." });
        }

        return Ok(MapToPrinterResponse(printer));
    }

    /// <summary>
    /// Gets the real-time connection/health status of a specific printer.
    /// This is a lightweight endpoint suitable for polling or dashboard displays.
    /// For push-based real-time updates, subscribe to the SignalR PrinterHub instead.
    /// </summary>
    /// <param name="id">The unique identifier of the printer.</param>
    /// <returns>The printer's current status, last-seen timestamp, and active flag.</returns>
    /// <response code="200">Returns the printer status.</response>
    /// <response code="404">The printer was not found.</response>
    [HttpGet("{id:guid}/status")]
    [ProducesResponseType(typeof(PrinterStatusResponse), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<ActionResult<PrinterStatusResponse>> GetPrinterStatus(Guid id)
    {
        _logger.LogDebug("Getting status for printer {Id}", id);

        var printer = await _dbContext.Printers.FindAsync(id);
        if (printer is null)
        {
            _logger.LogWarning("Printer {Id} not found for status check", id);
            return NotFound(new { error = $"Printer with ID '{id}' not found." });
        }

        return Ok(new PrinterStatusResponse
        {
            Id = printer.Id,
            Name = printer.Name,
            Status = printer.Status.ToString(),
            LastSeenAt = printer.LastSeenAt,
            IsActive = printer.IsActive
        });
    }

    /// <summary>
    /// Registers a new printer in the fleet.
    /// The printer is created with an initial status of Offline.
    /// The port defaults to 8883 for MQTT or 7125 for Moonraker if not specified.
    /// </summary>
    /// <param name="request">The printer registration request with connection details.</param>
    /// <returns>The newly created printer.</returns>
    /// <response code="201">The printer was created successfully.</response>
    /// <response code="400">The request body is invalid or validation failed.</response>
    [HttpPost]
    [ProducesResponseType(typeof(PrinterResponse), StatusCodes.Status201Created)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    public async Task<ActionResult<PrinterResponse>> CreatePrinter(
        [FromBody] CreatePrinterRequest request)
    {
        if (!ModelState.IsValid)
            return BadRequest(ModelState);

        _logger.LogInformation("Creating printer: {Name} ({Manufacturer} {Model})",
            request.Name, request.Manufacturer, request.Model);

        // Parse enums from string values
        if (!Enum.TryParse<PrinterType>(request.PrinterType, true, out var printerType))
        {
            return BadRequest(new { error = $"Invalid PrinterType: '{request.PrinterType}'. Must be 'Fdm' or 'Resin'." });
        }

        if (!Enum.TryParse<ConnectionType>(request.ConnectionType, true, out var connectionType))
        {
            return BadRequest(new { error = $"Invalid ConnectionType: '{request.ConnectionType}'. Must be 'Mqtt' or 'Moonraker'." });
        }

        // Default port based on connection type
        var port = request.Port > 0
            ? request.Port
            : connectionType == ConnectionType.Mqtt ? 8883 : 7125;

        var entity = new Printer
        {
            Name = request.Name,
            Manufacturer = request.Manufacturer,
            Model = request.Model,
            PrinterType = printerType,
            ConnectionType = connectionType,
            HostnameOrIp = request.HostnameOrIp,
            Port = port,
            MqttUsername = request.MqttUsername,
            MqttPassword = request.MqttPassword,
            MqttUseTls = request.MqttUseTls,
            ApiKey = request.ApiKey,
            IsActive = request.IsActive,
            Status = PrinterStatus.Offline
        };

        _dbContext.Printers.Add(entity);
        await _dbContext.SaveChangesAsync();

        var response = MapToPrinterResponse(entity);
        return CreatedAtAction(nameof(GetPrinter), new { id = entity.Id }, response);
    }

    /// <summary>
    /// Updates an existing printer's configuration and connection info.
    /// Uses full replacement semantics — all fields are written.
    /// The port defaults to 8883 for MQTT or 7125 for Moonraker if not specified.
    /// </summary>
    /// <param name="id">The unique identifier of the printer to update.</param>
    /// <param name="request">The printer update request with new values.</param>
    /// <returns>The updated printer.</returns>
    /// <response code="200">The printer was updated successfully.</response>
    /// <response code="400">The request body is invalid or validation failed.</response>
    /// <response code="404">The printer was not found.</response>
    [HttpPut("{id:guid}")]
    [ProducesResponseType(typeof(PrinterResponse), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    public async Task<ActionResult<PrinterResponse>> UpdatePrinter(
        Guid id, [FromBody] UpdatePrinterRequest request)
    {
        if (!ModelState.IsValid)
            return BadRequest(ModelState);

        _logger.LogInformation("Updating printer {Id}", id);

        var entity = await _dbContext.Printers.FindAsync(id);
        if (entity is null)
        {
            _logger.LogWarning("Printer {Id} not found for update", id);
            return NotFound(new { error = $"Printer with ID '{id}' not found." });
        }

        // Parse enums from string values
        if (!Enum.TryParse<PrinterType>(request.PrinterType, true, out var printerType))
        {
            return BadRequest(new { error = $"Invalid PrinterType: '{request.PrinterType}'. Must be 'Fdm' or 'Resin'." });
        }

        if (!Enum.TryParse<ConnectionType>(request.ConnectionType, true, out var connectionType))
        {
            return BadRequest(new { error = $"Invalid ConnectionType: '{request.ConnectionType}'. Must be 'Mqtt' or 'Moonraker'." });
        }

        var port = request.Port > 0
            ? request.Port
            : connectionType == ConnectionType.Mqtt ? 8883 : 7125;

        entity.Name = request.Name;
        entity.Manufacturer = request.Manufacturer;
        entity.Model = request.Model;
        entity.PrinterType = printerType;
        entity.ConnectionType = connectionType;
        entity.HostnameOrIp = request.HostnameOrIp;
        entity.Port = port;
        entity.MqttUsername = request.MqttUsername;
        entity.MqttPassword = request.MqttPassword;
        entity.MqttUseTls = request.MqttUseTls;
        entity.ApiKey = request.ApiKey;
        entity.IsActive = request.IsActive;

        await _dbContext.SaveChangesAsync();

        return Ok(MapToPrinterResponse(entity));
    }

    /// <summary>
    /// Soft-deletes a printer by setting <c>IsActive</c> to <c>false</c>.
    /// The printer record is retained for historical and audit purposes.
    /// Soft-deleted printers can be recovered via PUT with <c>IsActive = true</c>.
    /// </summary>
    /// <param name="id">The unique identifier of the printer to soft-delete.</param>
    /// <returns>No content on success.</returns>
    /// <response code="204">The printer was soft-deleted successfully.</response>
    /// <response code="404">The printer was not found.</response>
    [HttpDelete("{id:guid}")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> DeletePrinter(Guid id)
    {
        _logger.LogInformation("Soft-deleting printer {Id}", id);

        var entity = await _dbContext.Printers.FindAsync(id);
        if (entity is null)
        {
            _logger.LogWarning("Printer {Id} not found for soft-delete", id);
            return NotFound(new { error = $"Printer with ID '{id}' not found." });
        }

        entity.IsActive = false;
        await _dbContext.SaveChangesAsync();

        return NoContent();
    }

    // ── Mapping helper ───────────────────────────────────────────────

    /// <summary>
    /// Maps a <see cref="Printer"/> domain entity to a <see cref="PrinterResponse"/> DTO.
    /// </summary>
    /// <param name="p">The printer entity to map.</param>
    /// <returns>A response DTO suitable for API output.</returns>
    private static PrinterResponse MapToPrinterResponse(Printer p) => new()
    {
        Id = p.Id,
        Status = p.Status.ToString(),
        Name = p.Name,
        Manufacturer = p.Manufacturer,
        Model = p.Model,
        PrinterType = p.PrinterType.ToString(),
        ConnectionType = p.ConnectionType.ToString(),
        HostnameOrIp = p.HostnameOrIp,
        Port = p.Port,
        IsActive = p.IsActive,
        LastSeenAt = p.LastSeenAt,
        CreatedAt = p.CreatedAt,
        UpdatedAt = p.UpdatedAt
    };
}