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

import de.effigenix.application.inventory.AddStockBatch;
import de.effigenix.application.inventory.BlockStockBatch;
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.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.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;

    public StockController(CreateStock createStock, UpdateStock updateStock, GetStock getStock, ListStocks listStocks,
                           ListStocksBelowMinimum listStocksBelowMinimum,
                           AddStockBatch addStockBatch, RemoveStockBatch removeStockBatch,
                           BlockStockBatch blockStockBatch, UnblockStockBatch unblockStockBatch,
                           ReserveStock reserveStock, ReleaseReservation releaseReservation) {
        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;
    }

    @GetMapping
    @PreAuthorize("hasAuthority('STOCK_READ')")
    public ResponseEntity<List<StockResponse>> listStocks(
            @RequestParam(required = false) String storageLocationId,
            @RequestParam(required = false) String articleId
    ) {
        var result = listStocks.execute(storageLocationId, articleId);

        if (result.isFailure()) {
            throw new StockDomainErrorException(result.unsafeGetError());
        }

        List<StockResponse> responses = result.unsafeGetValue().stream()
                .map(StockResponse::from)
                .toList();
        return 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) {
        var result = listStocksBelowMinimum.execute(ActorId.of(authentication.getName()));

        if (result.isFailure()) {
            throw new StockDomainErrorException(result.unsafeGetError());
        }

        List<StockResponse> responses = result.unsafeGetValue().stream()
                .map(StockResponse::from)
                .toList();
        return ResponseEntity.ok(responses);
    }

    @GetMapping("/{id}")
    @PreAuthorize("hasAuthority('STOCK_READ')")
    public ResponseEntity<StockResponse> getStock(@PathVariable String id) {
        var result = getStock.execute(id);

        if (result.isFailure()) {
            throw new StockDomainErrorException(result.unsafeGetError());
        }

        return ResponseEntity.ok(StockResponse.from(result.unsafeGetValue()));
    }

    @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()
        );
        var result = createStock.execute(cmd);

        if (result.isFailure()) {
            throw new StockDomainErrorException(result.unsafeGetError());
        }

        logger.info("Stock created: {}", result.unsafeGetValue().id().value());
        return ResponseEntity.status(HttpStatus.CREATED)
                .body(CreateStockResponse.from(result.unsafeGetValue()));
    }

    @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()
        );
        var result = updateStock.execute(cmd);

        if (result.isFailure()) {
            throw new StockDomainErrorException(result.unsafeGetError());
        }

        logger.info("Stock updated: {}", id);
        return ResponseEntity.ok(StockResponse.from(result.unsafeGetValue()));
    }

    @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()
        );
        var result = addStockBatch.execute(cmd);

        if (result.isFailure()) {
            throw new StockDomainErrorException(result.unsafeGetError());
        }

        logger.info("Batch added: {}", result.unsafeGetValue().id().value());
        return ResponseEntity.status(HttpStatus.CREATED)
                .body(StockBatchResponse.from(result.unsafeGetValue()));
    }

    @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()
        );
        var result = removeStockBatch.execute(cmd);

        if (result.isFailure()) {
            throw new StockDomainErrorException(result.unsafeGetError());
        }

        logger.info("Batch removal completed for batch {} of stock {}", batchId, stockId);
        return 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());
        var result = blockStockBatch.execute(cmd, ActorId.of(authentication.getName()));

        if (result.isFailure()) {
            throw new StockDomainErrorException(result.unsafeGetError());
        }

        logger.info("Batch {} of stock {} blocked", batchId, stockId);
        return 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);
        var result = unblockStockBatch.execute(cmd, ActorId.of(authentication.getName()));

        if (result.isFailure()) {
            throw new StockDomainErrorException(result.unsafeGetError());
        }

        logger.info("Batch {} of stock {} unblocked", batchId, stockId);
        return 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()
        );
        var result = reserveStock.execute(cmd);

        if (result.isFailure()) {
            throw new StockDomainErrorException(result.unsafeGetError());
        }

        logger.info("Reservation created: {} for stock {}", result.unsafeGetValue().id().value(), stockId);
        return ResponseEntity.status(HttpStatus.CREATED)
                .body(ReservationResponse.from(result.unsafeGetValue()));
    }

    @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);
        var result = releaseReservation.execute(cmd);

        if (result.isFailure()) {
            throw new StockDomainErrorException(result.unsafeGetError());
        }

        logger.info("Reservation {} of stock {} released", reservationId, stockId);
        return 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;
        }
    }
}
