using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using MiGu.DB.Abstractions.Exceptions;
using MiGu.Server.Wms;

namespace MiGu.Server.Controllers;

[ApiController]
[Authorize]
[TypeFilter(typeof(WmsExceptionFilter))]
[Route("api/wms")]
public sealed class WmsController : ControllerBase
{
    private readonly WmsService _service;
    private readonly WmsTransportRuleService _transportRules;
    private readonly WmsTransportTaskService _transportTasks;

    public WmsController(WmsService service, WmsTransportRuleService transportRules, WmsTransportTaskService transportTasks)
    {
        _service = service;
        _transportRules = transportRules;
        _transportTasks = transportTasks;
    }

    [HttpGet("warehouses")]
    public Task<List<Warehouse>> Warehouses([FromQuery] string? q) => _service.Warehouses(q);

    [HttpPost("warehouses")]
    public async Task<IActionResult> SaveWarehouse([FromBody] MasterDataRequest req) =>
        Ok(await _service.SaveWarehouse(req, User.ActorName()));

    [HttpPut("warehouses/{id:guid}")]
    public async Task<IActionResult> UpdateWarehouse(Guid id, [FromBody] MasterDataRequest req) =>
        Ok(await _service.SaveWarehouse(req with { Id = id }, User.ActorName()));

    [HttpDelete("warehouses/{id:guid}")]
    public async Task<IActionResult> DeleteWarehouse(Guid id, [FromQuery] long? version)
    {
        await _service.DeleteEntity<Warehouse>(id, version, User.ActorName());
        return NoContent();
    }

    [HttpGet("areas")]
    public Task<List<WarehouseArea>> Areas([FromQuery] string? q, [FromQuery] Guid? warehouseId) =>
        _service.Areas(q, warehouseId);

    [HttpPost("areas")]
    public async Task<IActionResult> SaveArea([FromBody] AreaRequest req) =>
        Ok(await _service.SaveArea(req, User.ActorName()));

    [HttpPut("areas/{id:guid}")]
    public async Task<IActionResult> UpdateArea(Guid id, [FromBody] AreaRequest req) =>
        Ok(await _service.SaveArea(req with { Id = id }, User.ActorName()));

    [HttpDelete("areas/{id:guid}")]
    public async Task<IActionResult> DeleteArea(Guid id, [FromQuery] long? version)
    {
        await _service.DeleteEntity<WarehouseArea>(id, version, User.ActorName());
        return NoContent();
    }

    [HttpGet("storages")]
    public Task<List<Storage>> Storages([FromQuery] string? q, [FromQuery] Guid? areaId, [FromQuery] string? locationKind) =>
        _service.Storages(q, areaId, locationKind);

    [HttpPost("storages")]
    public async Task<IActionResult> SaveStorage([FromBody] StorageRequest req) =>
        Ok(await _service.SaveStorage(req, User.ActorName()));

    [HttpPut("storages/{id:guid}")]
    public async Task<IActionResult> UpdateStorage(Guid id, [FromBody] StorageRequest req) =>
        Ok(await _service.SaveStorage(req with { Id = id }, User.ActorName()));

    [HttpDelete("storages/{id:guid}")]
    public async Task<IActionResult> DeleteStorage(Guid id, [FromQuery] long? version)
    {
        await _service.DeleteEntity<Storage>(id, version, User.ActorName());
        return NoContent();
    }

    [HttpPost("areas/{id:guid}/generate-bins")]
    public async Task<IActionResult> GenerateBins(Guid id, [FromBody] GenerateBinsRequest req) =>
        Ok(new { created = await _service.GenerateBins(id, req, User.ActorName()) });

    [HttpPost("storages/{id:guid}/lock")]
    public async Task<IActionResult> LockStorage(Guid id, [FromQuery] long? version) =>
        Ok(await _service.SetStorageLock(id, true, version, User.ActorName()));

    [HttpPost("storages/{id:guid}/unlock")]
    public async Task<IActionResult> UnlockStorage(Guid id, [FromQuery] long? version) =>
        Ok(await _service.SetStorageLock(id, false, version, User.ActorName()));

    [HttpPost("storages/{id:guid}/enable")]
    public async Task<IActionResult> EnableStorage(Guid id, [FromQuery] long? version) =>
        Ok(await _service.SetStorageEnabled(id, true, version, User.ActorName()));

    [HttpPost("storages/{id:guid}/disable")]
    public async Task<IActionResult> DisableStorage(Guid id, [FromQuery] long? version) =>
        Ok(await _service.SetStorageEnabled(id, false, version, User.ActorName()));

    [HttpGet("containers")]
    public Task<List<Container>> Containers([FromQuery] string? q) => _service.Containers(q);

    [HttpPost("containers")]
    public async Task<IActionResult> SaveContainer([FromBody] ContainerRequest req) =>
        Ok(await _service.SaveContainer(req, User.ActorName()));

    [HttpPut("containers/{id:guid}")]
    public async Task<IActionResult> UpdateContainer(Guid id, [FromBody] ContainerRequest req) =>
        Ok(await _service.SaveContainer(req with { Id = id }, User.ActorName()));

    [HttpDelete("containers/{id:guid}")]
    public async Task<IActionResult> DeleteContainer(Guid id, [FromQuery] long? version)
    {
        await _service.DeleteEntity<Container>(id, version, User.ActorName());
        return NoContent();
    }

    [HttpGet("material-types")]
    public Task<List<MaterialType>> MaterialTypes([FromQuery] string? q) => _service.MaterialTypes(q);

    [HttpPost("material-types")]
    public async Task<IActionResult> SaveMaterialType([FromBody] MaterialTypeRequest req) =>
        Ok(await _service.SaveMaterialType(req, User.ActorName()));

    [HttpPut("material-types/{id:guid}")]
    public async Task<IActionResult> UpdateMaterialType(Guid id, [FromBody] MaterialTypeRequest req) =>
        Ok(await _service.SaveMaterialType(req with { Id = id }, User.ActorName()));

    [HttpDelete("material-types/{id:guid}")]
    public async Task<IActionResult> DeleteMaterialType(Guid id, [FromQuery] long? version)
    {
        await _service.DeleteEntity<MaterialType>(id, version, User.ActorName());
        return NoContent();
    }

    [HttpGet("materials")]
    public Task<List<Material>> Materials(
        [FromQuery] string? q,
        [FromQuery] string? lifecycle,
        [FromQuery] bool? onlyUnbound,
        [FromQuery] bool? onlyBound) =>
        _service.Materials(q, lifecycle, onlyUnbound, onlyBound);

    [HttpPost("materials")]
    public async Task<IActionResult> SaveMaterial([FromBody] MaterialRequest req) =>
        Ok(await _service.SaveMaterial(req, User.ActorName()));

    [HttpPut("materials/{id:guid}")]
    public async Task<IActionResult> UpdateMaterial(Guid id, [FromBody] MaterialRequest req) =>
        Ok(await _service.SaveMaterial(req with { Id = id }, User.ActorName()));

    [HttpDelete("materials/{id:guid}")]
    public async Task<IActionResult> DeleteMaterial(Guid id, [FromQuery] long? version)
    {
        await _service.DeleteEntity<Material>(id, version, User.ActorName());
        return NoContent();
    }

    [HttpPost("materials/{id:guid}/archive")]
    public async Task<IActionResult> ArchiveMaterial(Guid id, [FromQuery] long? version) =>
        Ok(await _service.ArchiveMaterial(id, version, User.ActorName()));

    [HttpPost("materials/purge-archived")]
    public async Task<IActionResult> PurgeArchivedMaterials([FromQuery] int take = 200)
    {
        await _service.PurgeArchivedMaterials(User.ActorName(), take);
        return NoContent();
    }

    [HttpGet("container-locations")]
    public Task<List<ContainerLocation>> ContainerLocations([FromQuery] string? locationType, [FromQuery] string? q) =>
        _service.ContainerLocations(locationType, q);

    [HttpPost("container-locations")]
    public async Task<IActionResult> SaveContainerLocation([FromBody] ContainerLocationRequest req) =>
        Ok(await _service.BindOrTransferLocation(req, User.ActorName()));

    [HttpPost("container-locations/transfer")]
    public async Task<IActionResult> TransferContainerLocation([FromBody] ContainerLocationRequest req) =>
        Ok(await _service.BindOrTransferLocation(req, User.ActorName()));

    [HttpDelete("container-locations/{containerId:guid}")]
    public async Task<IActionResult> UnbindContainerLocation(Guid containerId, [FromQuery] string? reason)
    {
        await _service.UnbindLocation(containerId, User.ActorName(), reason ?? "");
        return NoContent();
    }

    [HttpGet("container-materials")]
    public Task<List<ContainerMaterial>> ContainerMaterials([FromQuery] string? q, [FromQuery] Guid? containerId) =>
        _service.ContainerMaterials(q, containerId);

    [HttpPost("container-materials")]
    public async Task<IActionResult> SaveContainerMaterial([FromBody] BindMaterialRequest req) =>
        Ok(await _service.BindMaterial(req, User.ActorName()));

    [HttpPost("container-materials/bind")]
    public async Task<IActionResult> BindMaterial([FromBody] BindMaterialRequest req) =>
        Ok(await _service.BindMaterial(req, User.ActorName()));

    [HttpDelete("container-materials/{id:guid}")]
    public async Task<IActionResult> UnbindMaterial(Guid id, [FromQuery] string? reason, [FromQuery] bool archive = false)
    {
        await _service.UnbindMaterial(id, User.ActorName(), reason ?? "", archive);
        return NoContent();
    }

    [HttpGet("inventory/materials")]
    public Task<List<InventoryMaterialRow>> InventoryMaterials(
        [FromQuery] Guid? areaId, [FromQuery] Guid? storageId, [FromQuery] string? q) =>
        _service.InventoryMaterials(areaId, storageId, q);

    [HttpGet("stock-events")]
    public Task<List<StockEvent>> StockEvents(
        [FromQuery] string? eventType, [FromQuery] Guid? materialId, [FromQuery] Guid? containerId) =>
        _service.StockEvents(eventType, materialId, containerId);

    [HttpGet("container-location-history")]
    public Task<List<ContainerLocationHistory>> ContainerLocationHistory([FromQuery] Guid? containerId) =>
        _service.LocationHistory(containerId);

    [HttpGet("container-material-history")]
    public Task<List<ContainerMaterialHistory>> ContainerMaterialHistory([FromQuery] Guid? containerId, [FromQuery] Guid? materialId) =>
        _service.MaterialHistory(containerId, materialId);

    [HttpGet("transport-rules")]
    public Task<List<WmsTransportRule>> TransportRules([FromQuery] string? triggerType, [FromQuery] string? q) =>
        _transportRules.ListAsync(triggerType, q);

    [HttpPost("transport-rules")]
    public async Task<IActionResult> SaveTransportRule([FromBody] TransportRuleRequest req) =>
        Ok(await _transportRules.SaveAsync(req, User.ActorName()));

    [HttpPut("transport-rules/{id:guid}")]
    public async Task<IActionResult> UpdateTransportRule(Guid id, [FromBody] TransportRuleRequest req) =>
        Ok(await _transportRules.SaveAsync(req with { Id = id }, User.ActorName()));

    [HttpDelete("transport-rules/{id:guid}")]
    public async Task<IActionResult> DeleteTransportRule(Guid id, [FromQuery] long? version)
    {
        await _transportRules.DeleteAsync(id, version, User.ActorName());
        return NoContent();
    }

    [HttpGet("transport-candidates")]
    public async Task<IActionResult> TransportCandidates([FromQuery] TransportRequestQuery q) =>
        Ok(await _transportTasks.PreviewCandidatesAsync(q.ToRequest()));

    [HttpPost("transport-tasks/generate")]
    public async Task<IActionResult> GenerateTransportTask([FromBody] TransportRequestQuery q) =>
        Ok(await _transportTasks.GenerateAsync(q.ToRequest(), User.ActorName()));

    [HttpGet("transport-tasks")]
    public Task<List<WmsTransportTask>> TransportTasks([FromQuery] string? status, [FromQuery] string? triggerType) =>
        _transportTasks.ListAsync(status, triggerType);

    [HttpPost("transport-tasks/{id:guid}/reserve")]
    public async Task<IActionResult> ReserveTransportTask(Guid id, [FromQuery] string? reason) =>
        Ok(await _transportTasks.ReserveAsync(id, User.ActorName(), reason ?? ""));

    [HttpPost("transport-tasks/{id:guid}/cancel")]
    public async Task<IActionResult> CancelTransportTask(Guid id, [FromQuery] string? reason) =>
        Ok(await _transportTasks.CancelAsync(id, User.ActorName(), reason ?? ""));

    [HttpPost("transport-tasks/{id:guid}/complete")]
    public async Task<IActionResult> CompleteTransportTask(Guid id, [FromQuery] string? reason) =>
        Ok(await _transportTasks.CompleteAsync(id, User.ActorName(), reason ?? ""));

    [HttpPost("transport-tasks/{id:guid}/dispatch")]
    public async Task<IActionResult> DispatchTransportTask(Guid id) =>
        Ok(await _transportTasks.DispatchAsync(id, User.ActorName()));
}

public sealed class WmsExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        switch (context.Exception)
        {
            case ConcurrencyConflictException ex:
                context.Result = new ObjectResult(new { message = ex.Message }) { StatusCode = StatusCodes.Status409Conflict };
                break;
            case EntityLockedException ex:
                context.Result = new ObjectResult(new { message = ex.Message }) { StatusCode = StatusCodes.Status409Conflict };
                break;
            case EntityNotFoundException ex:
                context.Result = new NotFoundObjectResult(new { message = ex.Message });
                break;
            case InvalidOperationException ex:
                context.Result = new BadRequestObjectResult(new { message = ex.Message });
                break;
            default:
                return;
        }

        context.ExceptionHandled = true;
    }
}

public sealed class TransportRequestQuery
{
    public string TriggerType { get; set; } = nameof(WmsTransportTriggerType.MaterialCall);
    public Guid? RuleId { get; set; }
    public string? RequestSiteId { get; set; }
    public Guid? MaterialId { get; set; }
    public decimal? Quantity { get; set; }
    public Guid? ContainerId { get; set; }
    public Guid? SourceStorageId { get; set; }
    public Guid? TargetStorageId { get; set; }
    public string? BatchNo { get; set; }
    public int Priority { get; set; }
    public string Reason { get; set; } = "";

    public WmsTransportRequest ToRequest() => new(
        TriggerType, RuleId, RequestSiteId, MaterialId, Quantity,
        ContainerId, SourceStorageId, TargetStorageId, BatchNo,
        Priority, Reason, null);
}
