using Apollinare.API.Modules.Warehouse.Services;
using Apollinare.Domain.Entities.Warehouse;
using Microsoft.AspNetCore.Mvc;

namespace Apollinare.API.Modules.Warehouse.Controllers;

/// <summary>
/// Controller per la gestione dei movimenti di magazzino
/// </summary>
[ApiController]
[Route("api/warehouse/movements")]
public class StockMovementsController : ControllerBase
{
    private readonly IWarehouseService _warehouseService;
    private readonly ILogger<StockMovementsController> _logger;

    public StockMovementsController(
        IWarehouseService warehouseService,
        ILogger<StockMovementsController> logger)
    {
        _warehouseService = warehouseService;
        _logger = logger;
    }

    /// <summary>
    /// Ottiene la lista dei movimenti con filtri opzionali
    /// </summary>
    [HttpGet]
    public async Task<ActionResult<List<MovementDto>>> GetMovements([FromQuery] MovementFilterDto? filter)
    {
        var movementFilter = filter != null ? new MovementFilter
        {
            DateFrom = filter.DateFrom,
            DateTo = filter.DateTo,
            Type = filter.Type,
            Status = filter.Status,
            WarehouseId = filter.WarehouseId,
            ArticleId = filter.ArticleId,
            ReasonId = filter.ReasonId,
            ExternalReference = filter.ExternalReference,
            Skip = filter.Skip,
            Take = filter.Take,
            OrderBy = filter.OrderBy,
            OrderDescending = filter.OrderDescending
        } : null;

        var movements = await _warehouseService.GetMovementsAsync(movementFilter);
        return Ok(movements.Select(MapToDto));
    }

    /// <summary>
    /// Ottiene un movimento per ID
    /// </summary>
    [HttpGet("{id}")]
    public async Task<ActionResult<MovementDetailDto>> GetMovement(int id)
    {
        var movement = await _warehouseService.GetMovementByIdAsync(id);
        if (movement == null)
            return NotFound();

        return Ok(MapToDetailDto(movement));
    }

    /// <summary>
    /// Ottiene un movimento per numero documento
    /// </summary>
    [HttpGet("by-document/{documentNumber}")]
    public async Task<ActionResult<MovementDetailDto>> GetMovementByDocumentNumber(string documentNumber)
    {
        var movement = await _warehouseService.GetMovementByDocumentNumberAsync(documentNumber);
        if (movement == null)
            return NotFound();

        return Ok(MapToDetailDto(movement));
    }

    /// <summary>
    /// Crea un nuovo movimento (carico)
    /// </summary>
    [HttpPost("inbound")]
    public async Task<ActionResult<MovementDetailDto>> CreateInboundMovement([FromBody] CreateMovementDto dto)
    {
        return await CreateMovement(dto, MovementType.Inbound);
    }

    /// <summary>
    /// Crea un nuovo movimento (scarico)
    /// </summary>
    [HttpPost("outbound")]
    public async Task<ActionResult<MovementDetailDto>> CreateOutboundMovement([FromBody] CreateMovementDto dto)
    {
        return await CreateMovement(dto, MovementType.Outbound);
    }

    /// <summary>
    /// Crea un nuovo movimento (trasferimento)
    /// </summary>
    [HttpPost("transfer")]
    public async Task<ActionResult<MovementDetailDto>> CreateTransferMovement([FromBody] CreateTransferDto dto)
    {
        try
        {
            var movement = new StockMovement
            {
                DocumentNumber = dto.DocumentNumber ?? "",
                MovementDate = dto.MovementDate ?? DateTime.UtcNow,
                Type = MovementType.Transfer,
                ReasonId = dto.ReasonId,
                SourceWarehouseId = dto.SourceWarehouseId,
                DestinationWarehouseId = dto.DestinationWarehouseId,
                ExternalReference = dto.ExternalReference,
                Notes = dto.Notes,
                Status = MovementStatus.Draft,
                Lines = dto.Lines.Select((l, i) => new StockMovementLine
                {
                    LineNumber = i + 1,
                    ArticleId = l.ArticleId,
                    BatchId = l.BatchId,
                    SerialId = l.SerialId,
                    Quantity = l.Quantity,
                    UnitOfMeasure = l.UnitOfMeasure ?? "PZ",
                    Notes = l.Notes
                }).ToList()
            };

            var created = await _warehouseService.CreateMovementAsync(movement);
            return CreatedAtAction(nameof(GetMovement), new { id = created.Id }, MapToDetailDto(created));
        }
        catch (InvalidOperationException ex)
        {
            return BadRequest(new { error = ex.Message });
        }
    }

    /// <summary>
    /// Crea un nuovo movimento (rettifica)
    /// </summary>
    [HttpPost("adjustment")]
    public async Task<ActionResult<MovementDetailDto>> CreateAdjustmentMovement([FromBody] CreateAdjustmentDto dto)
    {
        try
        {
            var movement = new StockMovement
            {
                DocumentNumber = dto.DocumentNumber ?? "",
                MovementDate = dto.MovementDate ?? DateTime.UtcNow,
                Type = MovementType.Adjustment,
                ReasonId = dto.ReasonId,
                DestinationWarehouseId = dto.WarehouseId, // Per rettifiche positive
                SourceWarehouseId = dto.WarehouseId, // Per rettifiche negative
                ExternalReference = dto.ExternalReference,
                Notes = dto.Notes,
                Status = MovementStatus.Draft,
                Lines = dto.Lines.Select((l, i) => new StockMovementLine
                {
                    LineNumber = i + 1,
                    ArticleId = l.ArticleId,
                    BatchId = l.BatchId,
                    SerialId = l.SerialId,
                    Quantity = l.Quantity, // Positiva o negativa
                    UnitOfMeasure = l.UnitOfMeasure ?? "PZ",
                    UnitCost = l.UnitCost,
                    LineValue = l.Quantity * (l.UnitCost ?? 0),
                    Notes = l.Notes
                }).ToList()
            };

            var created = await _warehouseService.CreateMovementAsync(movement);
            return CreatedAtAction(nameof(GetMovement), new { id = created.Id }, MapToDetailDto(created));
        }
        catch (InvalidOperationException ex)
        {
            return BadRequest(new { error = ex.Message });
        }
    }

    private async Task<ActionResult<MovementDetailDto>> CreateMovement(CreateMovementDto dto, MovementType type)
    {
        try
        {
            var movement = new StockMovement
            {
                DocumentNumber = dto.DocumentNumber ?? "",
                MovementDate = dto.MovementDate ?? DateTime.UtcNow,
                Type = type,
                ReasonId = dto.ReasonId,
                SourceWarehouseId = type == MovementType.Outbound ? dto.WarehouseId : null,
                DestinationWarehouseId = type == MovementType.Inbound ? dto.WarehouseId : null,
                ExternalReference = dto.ExternalReference,
                ExternalDocumentType = dto.ExternalDocumentType,
                SupplierId = dto.SupplierId,
                CustomerId = dto.CustomerId,
                Notes = dto.Notes,
                Status = MovementStatus.Draft,
                Lines = dto.Lines.Select((l, i) => new StockMovementLine
                {
                    LineNumber = i + 1,
                    ArticleId = l.ArticleId,
                    BatchId = l.BatchId,
                    SerialId = l.SerialId,
                    Quantity = l.Quantity,
                    UnitOfMeasure = l.UnitOfMeasure ?? "PZ",
                    UnitCost = l.UnitCost,
                    LineValue = l.Quantity * (l.UnitCost ?? 0),
                    SourceLocationCode = l.SourceLocationCode,
                    DestinationLocationCode = l.DestinationLocationCode,
                    ExternalLineReference = l.ExternalLineReference,
                    Notes = l.Notes
                }).ToList()
            };

            var created = await _warehouseService.CreateMovementAsync(movement);
            return CreatedAtAction(nameof(GetMovement), new { id = created.Id }, MapToDetailDto(created));
        }
        catch (InvalidOperationException ex)
        {
            return BadRequest(new { error = ex.Message });
        }
    }

    /// <summary>
    /// Aggiorna un movimento esistente (solo bozze)
    /// </summary>
    [HttpPut("{id}")]
    public async Task<ActionResult<MovementDetailDto>> UpdateMovement(int id, [FromBody] UpdateMovementDto dto)
    {
        try
        {
            var existing = await _warehouseService.GetMovementByIdAsync(id);
            if (existing == null)
                return NotFound();

            existing.MovementDate = dto.MovementDate ?? existing.MovementDate;
            existing.ReasonId = dto.ReasonId ?? existing.ReasonId;
            existing.ExternalReference = dto.ExternalReference ?? existing.ExternalReference;
            existing.Notes = dto.Notes ?? existing.Notes;

            if (dto.Lines != null)
            {
                existing.Lines = dto.Lines.Select((l, i) => new StockMovementLine
                {
                    MovementId = id,
                    LineNumber = i + 1,
                    ArticleId = l.ArticleId,
                    BatchId = l.BatchId,
                    SerialId = l.SerialId,
                    Quantity = l.Quantity,
                    UnitOfMeasure = l.UnitOfMeasure ?? "PZ",
                    UnitCost = l.UnitCost,
                    LineValue = l.Quantity * (l.UnitCost ?? 0),
                    SourceLocationCode = l.SourceLocationCode,
                    DestinationLocationCode = l.DestinationLocationCode,
                    ExternalLineReference = l.ExternalLineReference,
                    Notes = l.Notes
                }).ToList();
            }

            var updated = await _warehouseService.UpdateMovementAsync(existing);
            return Ok(MapToDetailDto(updated));
        }
        catch (InvalidOperationException ex)
        {
            return BadRequest(new { error = ex.Message });
        }
    }

    /// <summary>
    /// Conferma un movimento (applica alle giacenze)
    /// </summary>
    [HttpPost("{id}/confirm")]
    public async Task<ActionResult<MovementDetailDto>> ConfirmMovement(int id)
    {
        try
        {
            var movement = await _warehouseService.ConfirmMovementAsync(id);
            return Ok(MapToDetailDto(movement));
        }
        catch (ArgumentException ex)
        {
            return NotFound(new { error = ex.Message });
        }
        catch (InvalidOperationException ex)
        {
            return BadRequest(new { error = ex.Message });
        }
    }

    /// <summary>
    /// Annulla un movimento
    /// </summary>
    [HttpPost("{id}/cancel")]
    public async Task<ActionResult<MovementDetailDto>> CancelMovement(int id)
    {
        try
        {
            var movement = await _warehouseService.CancelMovementAsync(id);
            return Ok(MapToDetailDto(movement));
        }
        catch (ArgumentException ex)
        {
            return NotFound(new { error = ex.Message });
        }
        catch (InvalidOperationException ex)
        {
            return BadRequest(new { error = ex.Message });
        }
    }

    /// <summary>
    /// Genera un nuovo numero documento
    /// </summary>
    [HttpGet("generate-number/{type}")]
    public async Task<ActionResult<string>> GenerateDocumentNumber(MovementType type)
    {
        var number = await _warehouseService.GenerateDocumentNumberAsync(type);
        return Ok(new { documentNumber = number });
    }

    /// <summary>
    /// Ottiene le causali movimento
    /// </summary>
    [HttpGet("reasons")]
    public async Task<ActionResult<List<MovementReasonDto>>> GetMovementReasons([FromQuery] MovementType? type = null, [FromQuery] bool includeInactive = false)
    {
        var reasons = await _warehouseService.GetMovementReasonsAsync(type, includeInactive);
        return Ok(reasons.Select(r => new MovementReasonDto(
            r.Id,
            r.Code,
            r.Description,
            r.MovementType,
            r.StockSign,
            r.RequiresExternalReference,
            r.RequiresValuation,
            r.UpdatesAverageCost,
            r.IsSystem,
            r.IsActive
        )));
    }

    #region DTOs

    public record MovementFilterDto(
        DateTime? DateFrom,
        DateTime? DateTo,
        MovementType? Type,
        MovementStatus? Status,
        int? WarehouseId,
        int? ArticleId,
        int? ReasonId,
        string? ExternalReference,
        int Skip = 0,
        int Take = 100,
        string? OrderBy = null,
        bool OrderDescending = true
    );

    public record MovementDto(
        int Id,
        string DocumentNumber,
        DateTime MovementDate,
        MovementType Type,
        MovementStatus Status,
        int? SourceWarehouseId,
        string? SourceWarehouseCode,
        string? SourceWarehouseName,
        int? DestinationWarehouseId,
        string? DestinationWarehouseCode,
        string? DestinationWarehouseName,
        int? ReasonId,
        string? ReasonDescription,
        string? ExternalReference,
        decimal? TotalValue,
        int LineCount,
        DateTime? ConfirmedDate,
        string? Notes,
        DateTime? CreatedAt
    );

    public record MovementDetailDto(
        int Id,
        string DocumentNumber,
        DateTime MovementDate,
        MovementType Type,
        MovementStatus Status,
        int? SourceWarehouseId,
        string? SourceWarehouseCode,
        string? SourceWarehouseName,
        int? DestinationWarehouseId,
        string? DestinationWarehouseCode,
        string? DestinationWarehouseName,
        int? ReasonId,
        string? ReasonDescription,
        string? ExternalReference,
        ExternalDocumentType? ExternalDocumentType,
        int? SupplierId,
        int? CustomerId,
        decimal? TotalValue,
        DateTime? ConfirmedDate,
        string? ConfirmedBy,
        string? Notes,
        DateTime? CreatedAt,
        DateTime? UpdatedAt,
        List<MovementLineDto> Lines
    );

    public record MovementLineDto(
        int Id,
        int LineNumber,
        int ArticleId,
        string ArticleCode,
        string ArticleDescription,
        int? BatchId,
        string? BatchNumber,
        int? SerialId,
        string? SerialNumber,
        decimal Quantity,
        string UnitOfMeasure,
        decimal? UnitCost,
        decimal? LineValue,
        string? SourceLocationCode,
        string? DestinationLocationCode,
        string? Notes
    );

    public record CreateMovementDto(
        string? DocumentNumber,
        DateTime? MovementDate,
        int? ReasonId,
        int WarehouseId,
        string? ExternalReference,
        ExternalDocumentType? ExternalDocumentType,
        int? SupplierId,
        int? CustomerId,
        string? Notes,
        List<CreateMovementLineDto> Lines
    );

    public record CreateTransferDto(
        string? DocumentNumber,
        DateTime? MovementDate,
        int? ReasonId,
        int SourceWarehouseId,
        int DestinationWarehouseId,
        string? ExternalReference,
        string? Notes,
        List<CreateMovementLineDto> Lines
    );

    public record CreateAdjustmentDto(
        string? DocumentNumber,
        DateTime? MovementDate,
        int? ReasonId,
        int WarehouseId,
        string? ExternalReference,
        string? Notes,
        List<CreateMovementLineDto> Lines
    );

    public record CreateMovementLineDto(
        int ArticleId,
        int? BatchId,
        int? SerialId,
        decimal Quantity,
        string? UnitOfMeasure,
        decimal? UnitCost,
        string? SourceLocationCode,
        string? DestinationLocationCode,
        string? ExternalLineReference,
        string? Notes
    );

    public record UpdateMovementDto(
        DateTime? MovementDate,
        int? ReasonId,
        string? ExternalReference,
        string? Notes,
        List<CreateMovementLineDto>? Lines
    );

    public record MovementReasonDto(
        int Id,
        string Code,
        string Description,
        MovementType MovementType,
        int StockSign,
        bool RequiresExternalReference,
        bool RequiresValuation,
        bool UpdatesAverageCost,
        bool IsSystem,
        bool IsActive
    );

    #endregion

    #region Mapping

    private static MovementDto MapToDto(StockMovement movement) => new(
        movement.Id,
        movement.DocumentNumber,
        movement.MovementDate,
        movement.Type,
        movement.Status,
        movement.SourceWarehouseId,
        movement.SourceWarehouse?.Code,
        movement.SourceWarehouse?.Name,
        movement.DestinationWarehouseId,
        movement.DestinationWarehouse?.Code,
        movement.DestinationWarehouse?.Name,
        movement.ReasonId,
        movement.Reason?.Description,
        movement.ExternalReference,
        movement.TotalValue,
        movement.Lines.Count,
        movement.ConfirmedDate,
        movement.Notes,
        movement.CreatedAt
    );

    private static MovementDetailDto MapToDetailDto(StockMovement movement) => new(
        movement.Id,
        movement.DocumentNumber,
        movement.MovementDate,
        movement.Type,
        movement.Status,
        movement.SourceWarehouseId,
        movement.SourceWarehouse?.Code,
        movement.SourceWarehouse?.Name,
        movement.DestinationWarehouseId,
        movement.DestinationWarehouse?.Code,
        movement.DestinationWarehouse?.Name,
        movement.ReasonId,
        movement.Reason?.Description,
        movement.ExternalReference,
        movement.ExternalDocumentType,
        movement.SupplierId,
        movement.CustomerId,
        movement.TotalValue,
        movement.ConfirmedDate,
        movement.ConfirmedBy,
        movement.Notes,
        movement.CreatedAt,
        movement.UpdatedAt,
        movement.Lines.Select(l => new MovementLineDto(
            l.Id,
            l.LineNumber,
            l.ArticleId,
            l.Article?.Code ?? "",
            l.Article?.Description ?? "",
            l.BatchId,
            l.Batch?.BatchNumber,
            l.SerialId,
            l.Serial?.SerialNumber,
            l.Quantity,
            l.UnitOfMeasure,
            l.UnitCost,
            l.LineValue,
            l.SourceLocationCode,
            l.DestinationLocationCode,
            l.Notes
        )).ToList()
    );

    #endregion
}
