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

import de.effigenix.application.production.CancelProductionOrder;
import de.effigenix.application.production.CompleteProductionOrder;
import de.effigenix.application.production.CreateProductionOrder;
import de.effigenix.application.production.GetProductionOrder;
import de.effigenix.application.production.ListProductionOrders;
import de.effigenix.application.production.ReleaseProductionOrder;
import de.effigenix.application.production.RescheduleProductionOrder;
import de.effigenix.application.production.StartProductionOrder;
import de.effigenix.application.production.command.CancelProductionOrderCommand;
import de.effigenix.application.production.command.CompleteProductionOrderCommand;
import de.effigenix.application.production.command.CreateProductionOrderCommand;
import de.effigenix.application.production.command.ReleaseProductionOrderCommand;
import de.effigenix.application.production.command.RescheduleProductionOrderCommand;
import de.effigenix.application.production.command.StartProductionOrderCommand;
import de.effigenix.domain.production.BatchRepository;
import de.effigenix.domain.production.ProductionOrder;
import de.effigenix.domain.production.ProductionOrderError;
import de.effigenix.domain.production.ProductionOrderStatus;
import de.effigenix.infrastructure.production.web.dto.CancelProductionOrderRequest;
import de.effigenix.infrastructure.production.web.dto.CreateProductionOrderRequest;
import de.effigenix.infrastructure.production.web.dto.ProductionOrderResponse;
import de.effigenix.infrastructure.production.web.dto.RescheduleProductionOrderRequest;

import de.effigenix.shared.security.ActorId;
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.format.annotation.DateTimeFormat;
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.time.LocalDate;
import java.util.List;

@RestController
@RequestMapping("/api/production/production-orders")
@SecurityRequirement(name = "Bearer Authentication")
@Tag(name = "Production Orders", description = "Production order management endpoints")
public class ProductionOrderController {

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

    private final CreateProductionOrder createProductionOrder;
    private final GetProductionOrder getProductionOrder;
    private final ReleaseProductionOrder releaseProductionOrder;
    private final RescheduleProductionOrder rescheduleProductionOrder;
    private final StartProductionOrder startProductionOrder;
    private final CompleteProductionOrder completeProductionOrder;
    private final CancelProductionOrder cancelProductionOrder;
    private final ListProductionOrders listProductionOrders;
    private final BatchRepository batchRepository;

    public ProductionOrderController(CreateProductionOrder createProductionOrder,
                                     GetProductionOrder getProductionOrder,
                                     ReleaseProductionOrder releaseProductionOrder,
                                     RescheduleProductionOrder rescheduleProductionOrder,
                                     StartProductionOrder startProductionOrder,
                                     CompleteProductionOrder completeProductionOrder,
                                     CancelProductionOrder cancelProductionOrder,
                                     ListProductionOrders listProductionOrders,
                                     BatchRepository batchRepository) {
        this.createProductionOrder = createProductionOrder;
        this.getProductionOrder = getProductionOrder;
        this.releaseProductionOrder = releaseProductionOrder;
        this.rescheduleProductionOrder = rescheduleProductionOrder;
        this.startProductionOrder = startProductionOrder;
        this.completeProductionOrder = completeProductionOrder;
        this.cancelProductionOrder = cancelProductionOrder;
        this.listProductionOrders = listProductionOrders;
        this.batchRepository = batchRepository;
    }

    @GetMapping
    @PreAuthorize("hasAuthority('PRODUCTION_ORDER_READ')")
    public ResponseEntity<List<ProductionOrderResponse>> listProductionOrders(
            @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate dateFrom,
            @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate dateTo,
            @RequestParam(required = false) ProductionOrderStatus status,
            Authentication authentication
    ) {
        logger.info("Listing production orders by actor: {}", authentication.getName());

        var actor = ActorId.of(authentication.getName());

        boolean hasDateRange = dateFrom != null && dateTo != null;
        boolean hasPartialDate = (dateFrom != null) != (dateTo != null);

        if (hasPartialDate) {
            return ResponseEntity.badRequest().build();
        }

        var result = hasDateRange && status != null
                ? listProductionOrders.executeByDateRangeAndStatus(dateFrom, dateTo, status, actor)
                : hasDateRange
                ? listProductionOrders.executeByDateRange(dateFrom, dateTo, actor)
                : status != null
                ? listProductionOrders.executeByStatus(status, actor)
                : listProductionOrders.execute(actor);

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

        var responses = result.unsafeGetValue().stream()
                .map(order -> ProductionOrderResponse.from(order, resolveBatchNumber(order)))
                .toList();
        return ResponseEntity.ok(responses);
    }

    @GetMapping("/{id}")
    @PreAuthorize("hasAuthority('PRODUCTION_ORDER_READ')")
    public ResponseEntity<ProductionOrderResponse> getProductionOrder(@PathVariable String id) {
        logger.info("Getting production order: {}", id);

        var result = getProductionOrder.execute(id);

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

        return ResponseEntity.ok(ProductionOrderResponse.from(result.unsafeGetValue(), resolveBatchNumber(result.unsafeGetValue())));
    }

    @PostMapping
    @PreAuthorize("hasAuthority('PRODUCTION_ORDER_WRITE')")
    public ResponseEntity<ProductionOrderResponse> createProductionOrder(
            @Valid @RequestBody CreateProductionOrderRequest request,
            Authentication authentication
    ) {
        logger.info("Creating production order for recipe: {} by actor: {}", request.recipeId(), authentication.getName());

        var cmd = new CreateProductionOrderCommand(
                request.recipeId(),
                request.plannedQuantity(),
                request.plannedQuantityUnit(),
                request.plannedDate(),
                request.priority(),
                request.notes()
        );

        var result = createProductionOrder.execute(cmd, ActorId.of(authentication.getName()));

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

        return ResponseEntity.status(HttpStatus.CREATED)
                .body(ProductionOrderResponse.from(result.unsafeGetValue(), resolveBatchNumber(result.unsafeGetValue())));
    }

    @PostMapping("/{id}/reschedule")
    @PreAuthorize("hasAuthority('PRODUCTION_ORDER_WRITE')")
    public ResponseEntity<ProductionOrderResponse> rescheduleProductionOrder(
            @PathVariable String id,
            @Valid @RequestBody RescheduleProductionOrderRequest request,
            Authentication authentication
    ) {
        logger.info("Rescheduling production order: {} to {} by actor: {}", id, request.newPlannedDate(), authentication.getName());

        var cmd = new RescheduleProductionOrderCommand(id, request.newPlannedDate());
        var result = rescheduleProductionOrder.execute(cmd, ActorId.of(authentication.getName()));

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

        return ResponseEntity.ok(ProductionOrderResponse.from(result.unsafeGetValue(), resolveBatchNumber(result.unsafeGetValue())));
    }

    @PostMapping("/{id}/release")
    @PreAuthorize("hasAuthority('PRODUCTION_ORDER_WRITE')")
    public ResponseEntity<ProductionOrderResponse> releaseProductionOrder(
            @PathVariable String id,
            Authentication authentication
    ) {
        logger.info("Releasing production order: {} by actor: {}", id, authentication.getName());

        var cmd = new ReleaseProductionOrderCommand(id);
        var result = releaseProductionOrder.execute(cmd, ActorId.of(authentication.getName()));

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

        return ResponseEntity.ok(ProductionOrderResponse.from(result.unsafeGetValue(), resolveBatchNumber(result.unsafeGetValue())));
    }

    @PostMapping("/{id}/start")
    @PreAuthorize("hasAuthority('PRODUCTION_ORDER_WRITE')")
    public ResponseEntity<ProductionOrderResponse> startProductionOrder(
            @PathVariable String id,
            Authentication authentication
    ) {
        logger.info("Starting production for order: {} by actor: {}", id, authentication.getName());

        var cmd = new StartProductionOrderCommand(id);
        var result = startProductionOrder.execute(cmd, ActorId.of(authentication.getName()));

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

        var startResult = result.unsafeGetValue();
        return ResponseEntity.ok(ProductionOrderResponse.from(
                startResult.order(),
                startResult.batch().batchNumber().value()));
    }

    private String resolveBatchNumber(ProductionOrder order) {
        if (order.batchId() == null) {
            return null;
        }
        return batchRepository.findById(order.batchId())
                .fold(err -> null, opt -> opt.map(batch -> batch.batchNumber().value()).orElse(null));
    }

    @PostMapping("/{id}/complete")
    @PreAuthorize("hasAuthority('PRODUCTION_ORDER_WRITE')")
    public ResponseEntity<ProductionOrderResponse> completeProductionOrder(
            @PathVariable String id,
            Authentication authentication
    ) {
        logger.info("Completing production order: {} by actor: {}", id, authentication.getName());

        var cmd = new CompleteProductionOrderCommand(id);
        var result = completeProductionOrder.execute(cmd, ActorId.of(authentication.getName()));

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

        return ResponseEntity.ok(ProductionOrderResponse.from(result.unsafeGetValue(), resolveBatchNumber(result.unsafeGetValue())));
    }

    @PostMapping("/{id}/cancel")
    @PreAuthorize("hasAuthority('PRODUCTION_ORDER_WRITE')")
    public ResponseEntity<ProductionOrderResponse> cancelProductionOrder(
            @PathVariable String id,
            @Valid @RequestBody CancelProductionOrderRequest request,
            Authentication authentication
    ) {
        logger.info("Cancelling production order: {} by actor: {}, reason: {}", id, authentication.getName(), request.reason());

        var cmd = new CancelProductionOrderCommand(id, request.reason());
        var result = cancelProductionOrder.execute(cmd, ActorId.of(authentication.getName()));

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

        return ResponseEntity.ok(ProductionOrderResponse.from(result.unsafeGetValue(), resolveBatchNumber(result.unsafeGetValue())));
    }

    public static class ProductionOrderDomainErrorException extends RuntimeException {
        private final ProductionOrderError error;

        public ProductionOrderDomainErrorException(ProductionOrderError error) {
            super(error.message());
            this.error = error;
        }

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