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

import de.effigenix.application.production.ActivateRecipe;
import de.effigenix.application.production.ArchiveRecipe;
import de.effigenix.application.production.AddProductionStep;
import de.effigenix.application.production.AddRecipeIngredient;
import de.effigenix.application.production.CreateRecipe;
import de.effigenix.application.production.GetRecipe;
import de.effigenix.application.production.ListRecipes;
import de.effigenix.application.production.RemoveProductionStep;
import de.effigenix.application.production.RemoveRecipeIngredient;
import de.effigenix.application.production.command.ActivateRecipeCommand;
import de.effigenix.application.production.command.ArchiveRecipeCommand;
import de.effigenix.application.production.command.AddProductionStepCommand;
import de.effigenix.application.production.command.AddRecipeIngredientCommand;
import de.effigenix.application.production.command.CreateRecipeCommand;
import de.effigenix.application.production.command.RemoveProductionStepCommand;
import de.effigenix.application.production.command.RemoveRecipeIngredientCommand;
import de.effigenix.domain.production.RecipeError;
import de.effigenix.domain.production.RecipeId;
import de.effigenix.domain.production.RecipeStatus;
import de.effigenix.infrastructure.production.web.dto.AddProductionStepRequest;
import de.effigenix.infrastructure.production.web.dto.AddRecipeIngredientRequest;
import de.effigenix.infrastructure.production.web.dto.CreateRecipeRequest;
import de.effigenix.infrastructure.production.web.dto.RecipeResponse;
import de.effigenix.infrastructure.production.web.dto.RecipeSummaryResponse;
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.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/recipes")
@SecurityRequirement(name = "Bearer Authentication")
@Tag(name = "Recipes", description = "Recipe management endpoints")
public class RecipeController {

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

    private final CreateRecipe createRecipe;
    private final GetRecipe getRecipe;
    private final ListRecipes listRecipes;
    private final AddRecipeIngredient addRecipeIngredient;
    private final RemoveRecipeIngredient removeRecipeIngredient;
    private final AddProductionStep addProductionStep;
    private final RemoveProductionStep removeProductionStep;
    private final ActivateRecipe activateRecipe;
    private final ArchiveRecipe archiveRecipe;

    public RecipeController(CreateRecipe createRecipe,
                            GetRecipe getRecipe,
                            ListRecipes listRecipes,
                            AddRecipeIngredient addRecipeIngredient,
                            RemoveRecipeIngredient removeRecipeIngredient,
                            AddProductionStep addProductionStep,
                            RemoveProductionStep removeProductionStep,
                            ActivateRecipe activateRecipe,
                            ArchiveRecipe archiveRecipe) {
        this.createRecipe = createRecipe;
        this.getRecipe = getRecipe;
        this.listRecipes = listRecipes;
        this.addRecipeIngredient = addRecipeIngredient;
        this.removeRecipeIngredient = removeRecipeIngredient;
        this.addProductionStep = addProductionStep;
        this.removeProductionStep = removeProductionStep;
        this.activateRecipe = activateRecipe;
        this.archiveRecipe = archiveRecipe;
    }

    @GetMapping("/{id}")
    @PreAuthorize("hasAuthority('RECIPE_READ')")
    public ResponseEntity<RecipeResponse> getRecipe(
            @PathVariable("id") String recipeId,
            Authentication authentication
    ) {
        var actorId = extractActorId(authentication);
        var result = getRecipe.execute(RecipeId.of(recipeId), actorId);

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

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

    @GetMapping
    @PreAuthorize("hasAuthority('RECIPE_READ')")
    public ResponseEntity<List<RecipeSummaryResponse>> listRecipes(
            @RequestParam(value = "status", required = false) String status,
            Authentication authentication
    ) {
        var actorId = extractActorId(authentication);

        RecipeStatus parsedStatus = null;
        if (status != null) {
            try {
                parsedStatus = RecipeStatus.valueOf(status);
            } catch (IllegalArgumentException e) {
                throw new RecipeDomainErrorException(
                        new RecipeError.ValidationFailure("Invalid status: " + status));
            }
        }

        var result = (parsedStatus != null)
                ? listRecipes.executeByStatus(parsedStatus, actorId)
                : listRecipes.execute(actorId);

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

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

    @PostMapping
    @PreAuthorize("hasAuthority('RECIPE_WRITE')")
    public ResponseEntity<RecipeResponse> createRecipe(
            @Valid @RequestBody CreateRecipeRequest request,
            Authentication authentication
    ) {
        var actorId = extractActorId(authentication);
        logger.info("Creating recipe: {} v{} by actor: {}", request.name(), request.version(), actorId.value());

        var cmd = new CreateRecipeCommand(
                request.name(), request.version(), request.type(), request.description(),
                request.yieldPercentage(), request.shelfLifeDays(),
                request.outputQuantity(), request.outputUom(), request.articleId()
        );
        var result = createRecipe.execute(cmd, actorId);

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

        logger.info("Recipe created: {} v{}", request.name(), request.version());
        return ResponseEntity.status(HttpStatus.CREATED).body(RecipeResponse.from(result.unsafeGetValue()));
    }

    @PostMapping("/{id}/ingredients")
    @PreAuthorize("hasAuthority('RECIPE_WRITE')")
    public ResponseEntity<RecipeResponse> addIngredient(
            @PathVariable("id") String recipeId,
            @Valid @RequestBody AddRecipeIngredientRequest request,
            Authentication authentication
    ) {
        var actorId = extractActorId(authentication);
        logger.info("Adding ingredient to recipe: {} by actor: {}", recipeId, actorId.value());

        var cmd = new AddRecipeIngredientCommand(
                recipeId, request.position(), request.articleId(),
                request.quantity(), request.uom(), request.subRecipeId(), request.substitutable()
        );
        var result = addRecipeIngredient.execute(cmd, actorId);

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

        logger.info("Ingredient added to recipe: {}", recipeId);
        return ResponseEntity.status(HttpStatus.CREATED).body(RecipeResponse.from(result.unsafeGetValue()));
    }

    @DeleteMapping("/{id}/ingredients/{ingredientId}")
    @PreAuthorize("hasAuthority('RECIPE_WRITE')")
    public ResponseEntity<Void> removeIngredient(
            @PathVariable("id") String recipeId,
            @PathVariable("ingredientId") String ingredientId,
            Authentication authentication
    ) {
        var actorId = extractActorId(authentication);
        logger.info("Removing ingredient {} from recipe: {} by actor: {}", ingredientId, recipeId, actorId.value());

        var cmd = new RemoveRecipeIngredientCommand(recipeId, ingredientId);
        var result = removeRecipeIngredient.execute(cmd, actorId);

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

        logger.info("Ingredient removed from recipe: {}", recipeId);
        return ResponseEntity.noContent().build();
    }

    @PostMapping("/{id}/steps")
    @PreAuthorize("hasAuthority('RECIPE_WRITE')")
    public ResponseEntity<RecipeResponse> addProductionStep(
            @PathVariable("id") String recipeId,
            @Valid @RequestBody AddProductionStepRequest request,
            Authentication authentication
    ) {
        var actorId = extractActorId(authentication);
        logger.info("Adding production step to recipe: {} by actor: {}", recipeId, actorId.value());

        var cmd = new AddProductionStepCommand(
                recipeId, request.stepNumber(), request.description(),
                request.durationMinutes(), request.temperatureCelsius()
        );
        var result = addProductionStep.execute(cmd, actorId);

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

        logger.info("Production step added to recipe: {}", recipeId);
        return ResponseEntity.status(HttpStatus.CREATED).body(RecipeResponse.from(result.unsafeGetValue()));
    }

    @DeleteMapping("/{id}/steps/{stepNumber}")
    @PreAuthorize("hasAuthority('RECIPE_WRITE')")
    public ResponseEntity<Void> removeProductionStep(
            @PathVariable("id") String recipeId,
            @PathVariable("stepNumber") int stepNumber,
            Authentication authentication
    ) {
        var actorId = extractActorId(authentication);
        logger.info("Removing production step {} from recipe: {} by actor: {}", stepNumber, recipeId, actorId.value());

        var cmd = new RemoveProductionStepCommand(recipeId, stepNumber);
        var result = removeProductionStep.execute(cmd, actorId);

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

        logger.info("Production step removed from recipe: {}", recipeId);
        return ResponseEntity.noContent().build();
    }

    @PostMapping("/{id}/activate")
    @PreAuthorize("hasAuthority('RECIPE_WRITE')")
    public ResponseEntity<RecipeResponse> activateRecipe(
            @PathVariable("id") String recipeId,
            Authentication authentication
    ) {
        var actorId = extractActorId(authentication);
        logger.info("Activating recipe: {} by actor: {}", recipeId, actorId.value());

        var cmd = new ActivateRecipeCommand(recipeId);
        var result = activateRecipe.execute(cmd, actorId);

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

        logger.info("Recipe activated: {}", recipeId);
        return ResponseEntity.ok(RecipeResponse.from(result.unsafeGetValue()));
    }

    @PostMapping("/{id}/archive")
    @PreAuthorize("hasAuthority('RECIPE_WRITE')")
    public ResponseEntity<RecipeResponse> archiveRecipe(
            @PathVariable("id") String recipeId,
            Authentication authentication
    ) {
        var actorId = extractActorId(authentication);
        logger.info("Archiving recipe: {} by actor: {}", recipeId, actorId.value());

        var cmd = new ArchiveRecipeCommand(recipeId);
        var result = archiveRecipe.execute(cmd, actorId);

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

        logger.info("Recipe archived: {}", recipeId);
        return ResponseEntity.ok(RecipeResponse.from(result.unsafeGetValue()));
    }

    private ActorId extractActorId(Authentication authentication) {
        if (authentication == null || authentication.getName() == null) {
            throw new IllegalStateException("No authentication found in SecurityContext");
        }
        return ActorId.of(authentication.getName());
    }

    public static class RecipeDomainErrorException extends RuntimeException {
        private final RecipeError error;

        public RecipeDomainErrorException(RecipeError error) {
            super(error.message());
            this.error = error;
        }

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