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

import de.effigenix.application.inventory.CancelInventoryCount;
import de.effigenix.application.inventory.CompleteInventoryCount;
import de.effigenix.application.inventory.CreateInventoryCount;
import de.effigenix.application.inventory.GetInventoryCount;
import de.effigenix.application.inventory.ListInventoryCounts;
import de.effigenix.application.inventory.RecordCountItem;
import de.effigenix.application.inventory.StartInventoryCount;
import de.effigenix.application.inventory.command.CancelInventoryCountCommand;
import de.effigenix.application.inventory.command.CompleteInventoryCountCommand;
import de.effigenix.application.inventory.command.CreateInventoryCountCommand;
import de.effigenix.application.inventory.command.RecordCountItemCommand;
import de.effigenix.domain.inventory.InventoryCountError;
import de.effigenix.infrastructure.inventory.web.dto.CancelInventoryCountRequest;
import de.effigenix.infrastructure.inventory.web.dto.CreateInventoryCountRequest;
import de.effigenix.infrastructure.inventory.web.dto.InventoryCountResponse;
import de.effigenix.infrastructure.inventory.web.dto.RecordCountItemRequest;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.security.UserLookupPort;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
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/inventory-counts")
@SecurityRequirement(name = "Bearer Authentication")
@Tag(name = "Inventory Counts", description = "Inventory count management endpoints")
public class InventoryCountController {

    private final CreateInventoryCount createInventoryCount;
    private final GetInventoryCount getInventoryCount;
    private final ListInventoryCounts listInventoryCounts;
    private final StartInventoryCount startInventoryCount;
    private final RecordCountItem recordCountItem;
    private final CompleteInventoryCount completeInventoryCount;
    private final CancelInventoryCount cancelInventoryCount;
    private final UserLookupPort userLookup;

    public InventoryCountController(CreateInventoryCount createInventoryCount,
                                     GetInventoryCount getInventoryCount,
                                     ListInventoryCounts listInventoryCounts,
                                     StartInventoryCount startInventoryCount,
                                     RecordCountItem recordCountItem,
                                     CompleteInventoryCount completeInventoryCount,
                                     CancelInventoryCount cancelInventoryCount,
                                     UserLookupPort userLookup) {
        this.createInventoryCount = createInventoryCount;
        this.getInventoryCount = getInventoryCount;
        this.listInventoryCounts = listInventoryCounts;
        this.startInventoryCount = startInventoryCount;
        this.recordCountItem = recordCountItem;
        this.completeInventoryCount = completeInventoryCount;
        this.cancelInventoryCount = cancelInventoryCount;
        this.userLookup = userLookup;
    }

    @PostMapping
    @PreAuthorize("hasAuthority('INVENTORY_COUNT_WRITE')")
    public ResponseEntity<InventoryCountResponse> createInventoryCount(
            @Valid @RequestBody CreateInventoryCountRequest request,
            Authentication authentication
    ) {
        var cmd = new CreateInventoryCountCommand(
                request.storageLocationId(),
                request.countDate(),
                authentication.getName()
        );

        return switch (createInventoryCount.execute(cmd, ActorId.of(authentication.getName()))) {
            case Result.Failure(var err) -> throw new InventoryCountDomainErrorException(err);
            case Result.Success(var count) -> ResponseEntity.status(HttpStatus.CREATED)
                    .body(InventoryCountResponse.from(count, userLookup));
        };
    }

    @GetMapping("/{id}")
    @PreAuthorize("hasAuthority('INVENTORY_COUNT_READ')")
    public ResponseEntity<InventoryCountResponse> getInventoryCount(
            @PathVariable String id,
            Authentication authentication
    ) {
        return switch (getInventoryCount.execute(id, ActorId.of(authentication.getName()))) {
            case Result.Failure(var err) -> throw new InventoryCountDomainErrorException(err);
            case Result.Success(var count) -> ResponseEntity.ok(InventoryCountResponse.from(count, userLookup));
        };
    }

    @GetMapping
    @PreAuthorize("hasAuthority('INVENTORY_COUNT_READ')")
    public ResponseEntity<List<InventoryCountResponse>> listInventoryCounts(
            @RequestParam(required = false) String storageLocationId,
            @RequestParam(required = false) String status,
            Authentication authentication
    ) {
        return switch (listInventoryCounts.execute(storageLocationId, status, ActorId.of(authentication.getName()))) {
            case Result.Failure(var err) -> throw new InventoryCountDomainErrorException(err);
            case Result.Success(var counts) -> {
                var responses = counts.stream()
                        .map(c -> InventoryCountResponse.from(c, userLookup))
                        .toList();
                yield ResponseEntity.ok(responses);
            }
        };
    }

    @PatchMapping("/{id}/start")
    @PreAuthorize("hasAuthority('INVENTORY_COUNT_WRITE')")
    public ResponseEntity<InventoryCountResponse> startInventoryCount(
            @PathVariable String id,
            Authentication authentication
    ) {
        return switch (startInventoryCount.execute(id, ActorId.of(authentication.getName()))) {
            case Result.Failure(var err) -> throw new InventoryCountDomainErrorException(err);
            case Result.Success(var count) -> ResponseEntity.ok(InventoryCountResponse.from(count, userLookup));
        };
    }

    @PatchMapping("/{id}/items/{itemId}")
    @PreAuthorize("hasAuthority('INVENTORY_COUNT_WRITE')")
    public ResponseEntity<InventoryCountResponse> recordCountItem(
            @PathVariable String id,
            @PathVariable String itemId,
            @Valid @RequestBody RecordCountItemRequest request,
            Authentication authentication
    ) {
        var cmd = new RecordCountItemCommand(id, itemId, request.actualQuantityAmount(), request.actualQuantityUnit());
        return switch (recordCountItem.execute(cmd, ActorId.of(authentication.getName()))) {
            case Result.Failure(var err) -> throw new InventoryCountDomainErrorException(err);
            case Result.Success(var count) -> ResponseEntity.ok(InventoryCountResponse.from(count, userLookup));
        };
    }

    @PostMapping("/{id}/complete")
    @PreAuthorize("hasAuthority('INVENTORY_COUNT_WRITE')")
    public ResponseEntity<InventoryCountResponse> completeInventoryCount(
            @PathVariable String id,
            Authentication authentication
    ) {
        var cmd = new CompleteInventoryCountCommand(id, authentication.getName());
        return switch (completeInventoryCount.execute(cmd, ActorId.of(authentication.getName()))) {
            case Result.Failure(var err) -> throw new InventoryCountDomainErrorException(err);
            case Result.Success(var count) -> ResponseEntity.ok(InventoryCountResponse.from(count, userLookup));
        };
    }

    @PostMapping("/{id}/cancel")
    @PreAuthorize("hasAuthority('INVENTORY_COUNT_WRITE')")
    public ResponseEntity<InventoryCountResponse> cancelInventoryCount(
            @PathVariable String id,
            @Valid @RequestBody CancelInventoryCountRequest request,
            Authentication authentication
    ) {
        var cmd = new CancelInventoryCountCommand(id, request.reason());
        return switch (cancelInventoryCount.execute(cmd, ActorId.of(authentication.getName()))) {
            case Result.Failure(var err) -> throw new InventoryCountDomainErrorException(err);
            case Result.Success(var count) -> ResponseEntity.ok(InventoryCountResponse.from(count, userLookup));
        };
    }

    // ==================== Exception Wrapper ====================

    public static class InventoryCountDomainErrorException extends RuntimeException {
        private final InventoryCountError error;

        public InventoryCountDomainErrorException(InventoryCountError error) {
            super(error.message());
            this.error = error;
        }

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