package de.effigenix.infrastructure.inventory.web.controller;

import de.effigenix.application.inventory.AddStockBatch;
import de.effigenix.application.inventory.BlockStockBatch;
import de.effigenix.application.inventory.ConfirmReservation;
import de.effigenix.application.inventory.CreateStock;
import de.effigenix.application.inventory.GetStock;
import de.effigenix.application.inventory.ListStocks;
import de.effigenix.application.inventory.ListStocksBelowMinimum;
import de.effigenix.application.inventory.ReleaseReservation;
import de.effigenix.application.inventory.RemoveStockBatch;
import de.effigenix.application.inventory.ReserveStock;
import de.effigenix.application.inventory.UnblockStockBatch;
import de.effigenix.application.inventory.UpdateStock;
import de.effigenix.application.inventory.command.AddStockBatchCommand;
import de.effigenix.application.inventory.command.ConfirmReservationCommand;
import de.effigenix.application.inventory.command.BlockStockBatchCommand;
import de.effigenix.application.inventory.command.CreateStockCommand;
import de.effigenix.application.inventory.command.RemoveStockBatchCommand;
import de.effigenix.application.inventory.command.ReleaseReservationCommand;
import de.effigenix.application.inventory.command.ReserveStockCommand;
import de.effigenix.application.inventory.command.UnblockStockBatchCommand;
import de.effigenix.application.inventory.command.UpdateStockCommand;
import de.effigenix.domain.inventory.StockError;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.infrastructure.inventory.web.dto.AddStockBatchRequest;
import de.effigenix.infrastructure.inventory.web.dto.BlockStockBatchRequest;
import de.effigenix.infrastructure.inventory.web.dto.CreateStockRequest;
import de.effigenix.infrastructure.inventory.web.dto.CreateStockResponse;
import de.effigenix.infrastructure.inventory.web.dto.RemoveStockBatchRequest;
import de.effigenix.infrastructure.inventory.web.dto.ReservationResponse;
import de.effigenix.infrastructure.inventory.web.dto.ReserveStockRequest;
import de.effigenix.infrastructure.inventory.web.dto.StockBatchResponse;
import de.effigenix.infrastructure.inventory.web.dto.StockResponse;
import de.effigenix.infrastructure.inventory.web.dto.UpdateStockRequest;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/inventory/stocks")
@SecurityRequirement(name = "Bearer Authentication")
@Tag(name = "Stocks", description = "Stock management endpoints")
public class StockController {

    private static final Logger logger = LoggerFactory.getLogger(StockController.class);

    private final CreateStock createStock;
    private final UpdateStock updateStock;
    private final GetStock getStock;
    private final ListStocks listStocks;
    private final ListStocksBelowMinimum listStocksBelowMinimum;
    private final AddStockBatch addStockBatch;
    private final RemoveStockBatch removeStockBatch;
    private final BlockStockBatch blockStockBatch;
    private final UnblockStockBatch unblockStockBatch;
    private final ReserveStock reserveStock;
    private final ReleaseReservation releaseReservation;
    private final ConfirmReservation confirmReservation;

    public StockController(CreateStock createStock, UpdateStock updateStock, GetStock getStock, ListStocks listStocks,
                           ListStocksBelowMinimum listStocksBelowMinimum,
                           AddStockBatch addStockBatch, RemoveStockBatch removeStockBatch,
                           BlockStockBatch blockStockBatch, UnblockStockBatch unblockStockBatch,
                           ReserveStock reserveStock, ReleaseReservation releaseReservation,
                           ConfirmReservation confirmReservation) {
        this.createStock = createStock;
        this.updateStock = updateStock;
        this.getStock = getStock;
        this.listStocks = listStocks;
        this.listStocksBelowMinimum = listStocksBelowMinimum;
        this.addStockBatch = addStockBatch;
        this.removeStockBatch = removeStockBatch;
        this.blockStockBatch = blockStockBatch;
        this.unblockStockBatch = unblockStockBatch;
        this.reserveStock = reserveStock;
        this.releaseReservation = releaseReservation;
        this.confirmReservation = confirmReservation;
    }

    @GetMapping
    @PreAuthorize("hasAuthority('STOCK_READ')")
    public ResponseEntity<List<StockResponse>> listStocks(
            @RequestParam(required = false) String storageLocationId,
            @RequestParam(required = false) String articleId
    ) {
        return switch (listStocks.execute(storageLocationId, articleId)) {
            case Result.Failure(var err) -> throw new StockDomainErrorException(err);
            case Result.Success(var stocks) -> {
                var responses = stocks.stream()
                        .map(StockResponse::from)
                        .toList();
                yield ResponseEntity.ok(responses);
            }
        };
    }

    // NOTE: Must be declared before /{id} to avoid Spring matching "below-minimum" as path variable
    @GetMapping("/below-minimum")
    @PreAuthorize("hasAuthority('STOCK_READ')")
    public ResponseEntity<List<StockResponse>> listStocksBelowMinimum(Authentication authentication) {
        return switch (listStocksBelowMinimum.execute(ActorId.of(authentication.getName()))) {
            case Result.Failure(var err) -> throw new StockDomainErrorException(err);
            case Result.Success(var stocks) -> {
                var responses = stocks.stream()
                        .map(StockResponse::from)
                        .toList();
                yield ResponseEntity.ok(responses);
            }
        };
    }

    @GetMapping("/{id}")
    @PreAuthorize("hasAuthority('STOCK_READ')")
    public ResponseEntity<StockResponse> getStock(@PathVariable String id) {
        return switch (getStock.execute(id)) {
            case Result.Failure(var err) -> throw new StockDomainErrorException(err);
            case Result.Success(var stock) -> ResponseEntity.ok(StockResponse.from(stock));
        };
    }

    @PostMapping
    @PreAuthorize("hasAuthority('STOCK_WRITE')")
    public ResponseEntity<CreateStockResponse> createStock(
            @Valid @RequestBody CreateStockRequest request,
            Authentication authentication
    ) {
        logger.info("Creating stock for article: {} at location: {} by actor: {}",
                request.articleId(), request.storageLocationId(), authentication.getName());

        var cmd = new CreateStockCommand(
                request.articleId(), request.storageLocationId(),
                request.minimumLevelAmount(), request.minimumLevelUnit(),
                request.minimumShelfLifeDays()
        );
        return switch (createStock.execute(cmd)) {
            case Result.Failure(var err) -> throw new StockDomainErrorException(err);
            case Result.Success(var stock) -> {
                logger.info("Stock created: {}", stock.id().value());
                yield ResponseEntity.status(HttpStatus.CREATED)
                        .body(CreateStockResponse.from(stock));
            }
        };
    }

    @PutMapping("/{id}")
    @PreAuthorize("hasAuthority('STOCK_WRITE')")
    public ResponseEntity<StockResponse> updateStock(
            @PathVariable String id,
            @Valid @RequestBody UpdateStockRequest request,
            Authentication authentication
    ) {
        logger.info("Updating stock: {} by actor: {}", id, authentication.getName());

        var cmd = new UpdateStockCommand(
                id, request.minimumLevelAmount(), request.minimumLevelUnit(),
                request.minimumShelfLifeDays()
        );
        return switch (updateStock.execute(cmd)) {
            case Result.Failure(var err) -> throw new StockDomainErrorException(err);
            case Result.Success(var stock) -> {
                logger.info("Stock updated: {}", id);
                yield ResponseEntity.ok(StockResponse.from(stock));
            }
        };
    }

    @PostMapping("/{stockId}/batches")
    @PreAuthorize("hasAuthority('STOCK_WRITE')")
    public ResponseEntity<StockBatchResponse> addBatch(
            @PathVariable String stockId,
            @Valid @RequestBody AddStockBatchRequest request,
            Authentication authentication
    ) {
        logger.info("Adding batch to stock: {} by actor: {}", stockId, authentication.getName());

        var cmd = new AddStockBatchCommand(
                stockId, request.batchId(), request.batchType(),
                request.quantityAmount(), request.quantityUnit(),
                request.expiryDate()
        );
        return switch (addStockBatch.execute(cmd)) {
            case Result.Failure(var err) -> throw new StockDomainErrorException(err);
            case Result.Success(var batch) -> {
                logger.info("Batch added: {}", batch.id().value());
                yield ResponseEntity.status(HttpStatus.CREATED)
                        .body(StockBatchResponse.from(batch));
            }
        };
    }

    @PostMapping("/{stockId}/batches/{batchId}/remove")
    @PreAuthorize("hasAuthority('STOCK_WRITE')")
    public ResponseEntity<Void> removeBatch(
            @PathVariable String stockId,
            @PathVariable String batchId,
            @Valid @RequestBody RemoveStockBatchRequest request,
            Authentication authentication
    ) {
        logger.info("Removing from batch {} of stock {} by actor: {}", batchId, stockId, authentication.getName());

        var cmd = new RemoveStockBatchCommand(
                stockId, batchId,
                request.quantityAmount(), request.quantityUnit()
        );
        return switch (removeStockBatch.execute(cmd)) {
            case Result.Failure(var err) -> throw new StockDomainErrorException(err);
            case Result.Success(var ignored) -> {
                logger.info("Batch removal completed for batch {} of stock {}", batchId, stockId);
                yield ResponseEntity.ok().build();
            }
        };
    }

    @PostMapping("/{stockId}/batches/{batchId}/block")
    @PreAuthorize("hasAuthority('STOCK_WRITE')")
    public ResponseEntity<Void> blockBatch(
            @PathVariable String stockId,
            @PathVariable String batchId,
            @Valid @RequestBody BlockStockBatchRequest request,
            Authentication authentication
    ) {
        logger.info("Blocking batch {} of stock {} by actor: {}", batchId, stockId, authentication.getName());

        var cmd = new BlockStockBatchCommand(stockId, batchId, request.reason());
        return switch (blockStockBatch.execute(cmd, ActorId.of(authentication.getName()))) {
            case Result.Failure(var err) -> throw new StockDomainErrorException(err);
            case Result.Success(var ignored) -> {
                logger.info("Batch {} of stock {} blocked", batchId, stockId);
                yield ResponseEntity.ok().build();
            }
        };
    }

    @PostMapping("/{stockId}/batches/{batchId}/unblock")
    @PreAuthorize("hasAuthority('STOCK_WRITE')")
    public ResponseEntity<Void> unblockBatch(
            @PathVariable String stockId,
            @PathVariable String batchId,
            Authentication authentication
    ) {
        logger.info("Unblocking batch {} of stock {} by actor: {}", batchId, stockId, authentication.getName());

        var cmd = new UnblockStockBatchCommand(stockId, batchId);
        return switch (unblockStockBatch.execute(cmd, ActorId.of(authentication.getName()))) {
            case Result.Failure(var err) -> throw new StockDomainErrorException(err);
            case Result.Success(var ignored) -> {
                logger.info("Batch {} of stock {} unblocked", batchId, stockId);
                yield ResponseEntity.ok().build();
            }
        };
    }

    @PostMapping("/{stockId}/reservations")
    @PreAuthorize("hasAuthority('STOCK_WRITE')")
    public ResponseEntity<ReservationResponse> reserveStock(
            @PathVariable String stockId,
            @Valid @RequestBody ReserveStockRequest request,
            Authentication authentication
    ) {
        logger.info("Reserving stock {} by actor: {}", stockId, authentication.getName());

        var cmd = new ReserveStockCommand(
                stockId, request.referenceType(), request.referenceId(),
                request.quantityAmount(), request.quantityUnit(), request.priority()
        );
        return switch (reserveStock.execute(cmd)) {
            case Result.Failure(var err) -> throw new StockDomainErrorException(err);
            case Result.Success(var reservation) -> {
                logger.info("Reservation created: {} for stock {}", reservation.id().value(), stockId);
                yield ResponseEntity.status(HttpStatus.CREATED)
                        .body(ReservationResponse.from(reservation));
            }
        };
    }

    @DeleteMapping("/{stockId}/reservations/{reservationId}")
    @PreAuthorize("hasAuthority('STOCK_WRITE')")
    public ResponseEntity<Void> releaseReservation(
            @PathVariable String stockId,
            @PathVariable String reservationId,
            Authentication authentication
    ) {
        logger.info("Releasing reservation {} of stock {} by actor: {}", reservationId, stockId, authentication.getName());

        var cmd = new ReleaseReservationCommand(stockId, reservationId);
        return switch (releaseReservation.execute(cmd)) {
            case Result.Failure(var err) -> throw new StockDomainErrorException(err);
            case Result.Success(var ignored) -> {
                logger.info("Reservation {} of stock {} released", reservationId, stockId);
                yield ResponseEntity.noContent().build();
            }
        };
    }

    @PostMapping("/{stockId}/reservations/{reservationId}/confirm")
    @PreAuthorize("hasAuthority('STOCK_WRITE')")
    public ResponseEntity<Void> confirmReservation(
            @PathVariable String stockId,
            @PathVariable String reservationId,
            Authentication authentication
    ) {
        logger.info("Confirming reservation {} of stock {} by actor: {}", reservationId, stockId, authentication.getName());

        var cmd = new ConfirmReservationCommand(stockId, reservationId, authentication.getName());
        return switch (confirmReservation.execute(cmd)) {
            case Result.Failure(var err) -> throw new StockDomainErrorException(err);
            case Result.Success(var ignored) -> {
                logger.info("Reservation {} of stock {} confirmed", reservationId, stockId);
                yield ResponseEntity.noContent().build();
            }
        };
    }

    public static class StockDomainErrorException extends RuntimeException {
        private final StockError error;

        public StockDomainErrorException(StockError error) {
            super(error.message());
            this.error = error;
        }

        public StockError getError() {
            return error;
        }
    }
}
