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

import de.effigenix.application.production.CancelBatch;
import de.effigenix.application.production.CompleteBatch;
import de.effigenix.application.production.FindBatchByNumber;
import de.effigenix.application.production.GetBatch;
import de.effigenix.application.production.ListBatches;
import de.effigenix.application.production.PlanBatch;
import de.effigenix.application.production.RecordConsumption;
import de.effigenix.application.production.StartBatch;
import de.effigenix.application.production.TraceBatchBackward;
import de.effigenix.application.production.TraceBatchForward;
import de.effigenix.application.production.command.CancelBatchCommand;
import de.effigenix.application.production.command.CompleteBatchCommand;
import de.effigenix.application.production.command.PlanBatchCommand;
import de.effigenix.application.production.command.RecordConsumptionCommand;
import de.effigenix.application.production.command.StartBatchCommand;
import de.effigenix.application.production.command.TraceBatchBackwardCommand;
import de.effigenix.application.production.command.TraceBatchForwardCommand;
import de.effigenix.domain.production.BatchError;
import de.effigenix.domain.production.BatchId;
import de.effigenix.domain.production.BatchNumber;
import de.effigenix.domain.production.BatchStatus;
import de.effigenix.infrastructure.production.web.dto.BatchResponse;
import de.effigenix.infrastructure.production.web.dto.BatchSummaryResponse;
import de.effigenix.infrastructure.production.web.dto.ConsumptionResponse;
import de.effigenix.infrastructure.production.web.dto.TraceBatchBackwardResponse;
import de.effigenix.infrastructure.production.web.dto.TraceBatchForwardResponse;
import de.effigenix.infrastructure.production.web.dto.CancelBatchRequest;
import de.effigenix.infrastructure.production.web.dto.CompleteBatchRequest;
import de.effigenix.infrastructure.production.web.dto.PlanBatchRequest;
import de.effigenix.infrastructure.production.web.dto.RecordConsumptionRequest;
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/batches")
@SecurityRequirement(name = "Bearer Authentication")
@Tag(name = "Batches", description = "Production batch management endpoints")
public class BatchController {

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

    private final PlanBatch planBatch;
    private final GetBatch getBatch;
    private final ListBatches listBatches;
    private final FindBatchByNumber findBatchByNumber;
    private final StartBatch startBatch;
    private final RecordConsumption recordConsumption;
    private final CompleteBatch completeBatch;
    private final CancelBatch cancelBatch;
    private final TraceBatchForward traceBatchForward;
    private final TraceBatchBackward traceBatchBackward;

    public BatchController(PlanBatch planBatch, GetBatch getBatch, ListBatches listBatches,
                           FindBatchByNumber findBatchByNumber, StartBatch startBatch,
                           RecordConsumption recordConsumption, CompleteBatch completeBatch,
                           CancelBatch cancelBatch, TraceBatchForward traceBatchForward,
                           TraceBatchBackward traceBatchBackward) {
        this.planBatch = planBatch;
        this.getBatch = getBatch;
        this.listBatches = listBatches;
        this.findBatchByNumber = findBatchByNumber;
        this.startBatch = startBatch;
        this.recordConsumption = recordConsumption;
        this.completeBatch = completeBatch;
        this.cancelBatch = cancelBatch;
        this.traceBatchForward = traceBatchForward;
        this.traceBatchBackward = traceBatchBackward;
    }

    @GetMapping("/{id}")
    @PreAuthorize("hasAuthority('BATCH_READ')")
    public ResponseEntity<BatchResponse> getBatch(
            @PathVariable("id") String id,
            Authentication authentication
    ) {
        var actorId = ActorId.of(authentication.getName());
        var result = getBatch.execute(BatchId.of(id), actorId);

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

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

    @GetMapping
    @PreAuthorize("hasAuthority('BATCH_READ')")
    public ResponseEntity<List<BatchSummaryResponse>> listBatches(
            @RequestParam(value = "status", required = false) String status,
            @RequestParam(value = "productionDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate productionDate,
            @RequestParam(value = "articleId", required = false) String articleId,
            Authentication authentication
    ) {
        var actorId = ActorId.of(authentication.getName());

        var result = switch (filterType(status, productionDate, articleId)) {
            case "ambiguous" -> throw new BatchDomainErrorException(
                    new BatchError.ValidationFailure("Only one filter allowed at a time: status, productionDate, or articleId"));
            case "status" -> {
                try {
                    yield listBatches.executeByStatus(BatchStatus.valueOf(status), actorId);
                } catch (IllegalArgumentException e) {
                    throw new BatchDomainErrorException(
                            new BatchError.ValidationFailure("Invalid status: " + status));
                }
            }
            case "productionDate" -> listBatches.executeByProductionDate(productionDate, actorId);
            case "articleId" -> listBatches.executeByArticleId(articleId, actorId);
            default -> listBatches.execute(actorId);
        };

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

        var summaries = result.unsafeGetValue().stream()
                .map(BatchSummaryResponse::from)
                .toList();
        return ResponseEntity.ok(summaries);
    }

    @GetMapping("/by-number/{batchNumber}")
    @PreAuthorize("hasAuthority('BATCH_READ')")
    public ResponseEntity<BatchResponse> findByNumber(
            @PathVariable("batchNumber") String batchNumber,
            Authentication authentication
    ) {
        var actorId = ActorId.of(authentication.getName());

        var parsedNumber = BatchNumber.of(batchNumber);
        if (parsedNumber.isFailure()) {
            throw new BatchDomainErrorException(
                    new BatchError.ValidationFailure("Invalid batch number format: " + batchNumber));
        }

        var result = findBatchByNumber.execute(parsedNumber.unsafeGetValue(), actorId);

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

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

    @PostMapping
    @PreAuthorize("hasAuthority('BATCH_WRITE')")
    public ResponseEntity<BatchResponse> planBatch(
            @Valid @RequestBody PlanBatchRequest request,
            Authentication authentication
    ) {
        logger.info("Planning batch for recipe: {} by actor: {}", request.recipeId(), authentication.getName());

        var cmd = new PlanBatchCommand(
                request.recipeId(),
                request.plannedQuantity(),
                request.plannedQuantityUnit(),
                request.productionDate(),
                request.bestBeforeDate()
        );

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

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

        return ResponseEntity.status(HttpStatus.CREATED)
                .body(BatchResponse.from(result.unsafeGetValue()));
    }

    @PostMapping("/{id}/start")
    @PreAuthorize("hasAuthority('BATCH_WRITE')")
    public ResponseEntity<BatchResponse> startBatch(
            @PathVariable("id") String id,
            Authentication authentication
    ) {
        logger.info("Starting batch: {} by actor: {}", id, authentication.getName());

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

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

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

    @PostMapping("/{id}/consumptions")
    @PreAuthorize("hasAuthority('BATCH_WRITE')")
    public ResponseEntity<ConsumptionResponse> recordConsumption(
            @PathVariable("id") String id,
            @Valid @RequestBody RecordConsumptionRequest request,
            Authentication authentication
    ) {
        logger.info("Recording consumption for batch: {} by actor: {}", id, authentication.getName());

        var cmd = new RecordConsumptionCommand(
                id,
                request.inputBatchId(),
                request.articleId(),
                request.quantityUsed(),
                request.quantityUnit()
        );

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

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

        return ResponseEntity.status(HttpStatus.CREATED)
                .body(ConsumptionResponse.from(result.unsafeGetValue()));
    }

    @PostMapping("/{id}/complete")
    @PreAuthorize("hasAuthority('BATCH_COMPLETE')")
    public ResponseEntity<BatchResponse> completeBatch(
            @PathVariable("id") String id,
            @Valid @RequestBody CompleteBatchRequest request,
            Authentication authentication
    ) {
        logger.info("Completing batch: {} by actor: {}", id, authentication.getName());

        var cmd = new CompleteBatchCommand(
                id,
                request.actualQuantity(),
                request.actualQuantityUnit(),
                request.waste(),
                request.wasteUnit(),
                request.remarks()
        );

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

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

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

    @GetMapping("/{id}/trace-forward")
    @PreAuthorize("hasAuthority('BATCH_READ')")
    public ResponseEntity<TraceBatchForwardResponse> traceForward(
            @PathVariable("id") String id,
            Authentication authentication
    ) {
        var actorId = ActorId.of(authentication.getName());
        var cmd = new TraceBatchForwardCommand(id);
        var result = traceBatchForward.execute(cmd, actorId);

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

        return ResponseEntity.ok(TraceBatchForwardResponse.from(id, result.unsafeGetValue()));
    }

    @GetMapping("/{id}/trace-backward")
    @PreAuthorize("hasAuthority('BATCH_READ')")
    public ResponseEntity<TraceBatchBackwardResponse> traceBackward(
            @PathVariable("id") String id,
            Authentication authentication
    ) {
        var actorId = ActorId.of(authentication.getName());
        var cmd = new TraceBatchBackwardCommand(id);
        var result = traceBatchBackward.execute(cmd, actorId);

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

        return ResponseEntity.ok(TraceBatchBackwardResponse.from(id, result.unsafeGetValue()));
    }

    @PostMapping("/{id}/cancel")
    @PreAuthorize("hasAuthority('BATCH_CANCEL')")
    public ResponseEntity<BatchResponse> cancelBatch(
            @PathVariable("id") String id,
            @Valid @RequestBody CancelBatchRequest request,
            Authentication authentication
    ) {
        logger.info("Cancelling batch: {} by actor: {}", id, authentication.getName());

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

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

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

    private static String filterType(String status, LocalDate productionDate, String articleId) {
        int count = (status != null ? 1 : 0) + (productionDate != null ? 1 : 0) + (articleId != null ? 1 : 0);
        if (count > 1) return "ambiguous";
        if (status != null) return "status";
        if (productionDate != null) return "productionDate";
        if (articleId != null) return "articleId";
        return "none";
    }

    public static class BatchDomainErrorException extends RuntimeException {
        private final BatchError error;

        public BatchDomainErrorException(BatchError error) {
            super(error.message());
            this.error = error;
        }

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