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

import de.effigenix.application.masterdata.customer.*;
import de.effigenix.application.masterdata.customer.command.*;
import de.effigenix.domain.masterdata.customer.Customer;
import de.effigenix.domain.masterdata.customer.CustomerError;
import de.effigenix.domain.masterdata.customer.CustomerId;
import de.effigenix.domain.masterdata.customer.CustomerStatus;
import de.effigenix.domain.masterdata.customer.CustomerType;
import de.effigenix.infrastructure.masterdata.web.dto.*;
import de.effigenix.shared.common.Result;
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;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/api/customers")
@SecurityRequirement(name = "Bearer Authentication")
@Tag(name = "Customers", description = "Customer management endpoints")
public class CustomerController {

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

    private final CreateCustomer createCustomer;
    private final UpdateCustomer updateCustomer;
    private final GetCustomer getCustomer;
    private final ListCustomers listCustomers;
    private final ActivateCustomer activateCustomer;
    private final DeactivateCustomer deactivateCustomer;
    private final AddDeliveryAddress addDeliveryAddress;
    private final RemoveDeliveryAddress removeDeliveryAddress;
    private final SetFrameContract setFrameContract;
    private final RemoveFrameContract removeFrameContract;
    private final SetPreferences setPreferences;

    public CustomerController(
            CreateCustomer createCustomer,
            UpdateCustomer updateCustomer,
            GetCustomer getCustomer,
            ListCustomers listCustomers,
            ActivateCustomer activateCustomer,
            DeactivateCustomer deactivateCustomer,
            AddDeliveryAddress addDeliveryAddress,
            RemoveDeliveryAddress removeDeliveryAddress,
            SetFrameContract setFrameContract,
            RemoveFrameContract removeFrameContract,
            SetPreferences setPreferences
    ) {
        this.createCustomer = createCustomer;
        this.updateCustomer = updateCustomer;
        this.getCustomer = getCustomer;
        this.listCustomers = listCustomers;
        this.activateCustomer = activateCustomer;
        this.deactivateCustomer = deactivateCustomer;
        this.addDeliveryAddress = addDeliveryAddress;
        this.removeDeliveryAddress = removeDeliveryAddress;
        this.setFrameContract = setFrameContract;
        this.removeFrameContract = removeFrameContract;
        this.setPreferences = setPreferences;
    }

    @PostMapping
    @PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
    public ResponseEntity<CustomerResponse> createCustomer(
            @Valid @RequestBody CreateCustomerRequest request,
            Authentication authentication
    ) {
        var actorId = extractActorId(authentication);
        logger.info("Creating customer: {} by actor: {}", request.name(), actorId.value());

        var cmd = new CreateCustomerCommand(
                request.name(), request.type(),
                request.street(), request.houseNumber(), request.postalCode(),
                request.city(), request.country(),
                request.phone(), request.email(), request.contactPerson(),
                request.paymentDueDays(), request.paymentDescription()
        );
        var result = createCustomer.execute(cmd, actorId);

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

        logger.info("Customer created: {}", request.name());
        return ResponseEntity.status(HttpStatus.CREATED).body(CustomerResponse.from(result.unsafeGetValue()));
    }

    @GetMapping
    public ResponseEntity<List<CustomerResponse>> listCustomers(
            @RequestParam(value = "type", required = false) CustomerType type,
            @RequestParam(value = "status", required = false) CustomerStatus status,
            Authentication authentication
    ) {
        var actorId = extractActorId(authentication);
        logger.info("Listing customers by actor: {}", actorId.value());

        Result<CustomerError, List<Customer>> result;
        if (type != null) {
            result = listCustomers.executeByType(type);
        } else if (status != null) {
            result = listCustomers.executeByStatus(status);
        } else {
            result = listCustomers.execute();
        }

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

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

    @GetMapping("/{id}")
    public ResponseEntity<CustomerResponse> getCustomer(
            @PathVariable("id") String customerId,
            Authentication authentication
    ) {
        var actorId = extractActorId(authentication);
        logger.info("Getting customer: {} by actor: {}", customerId, actorId.value());

        var result = getCustomer.execute(CustomerId.of(customerId));

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

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

    @PutMapping("/{id}")
    @PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
    public ResponseEntity<CustomerResponse> updateCustomer(
            @PathVariable("id") String customerId,
            @Valid @RequestBody UpdateCustomerRequest request,
            Authentication authentication
    ) {
        var actorId = extractActorId(authentication);
        logger.info("Updating customer: {} by actor: {}", customerId, actorId.value());

        var cmd = new UpdateCustomerCommand(
                customerId,
                request.name(),
                request.street(), request.houseNumber(), request.postalCode(),
                request.city(), request.country(),
                request.phone(), request.email(), request.contactPerson(),
                request.paymentDueDays(), request.paymentDescription()
        );
        var result = updateCustomer.execute(cmd, actorId);

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

        logger.info("Customer updated: {}", customerId);
        return ResponseEntity.ok(CustomerResponse.from(result.unsafeGetValue()));
    }

    @PostMapping("/{id}/activate")
    @PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
    public ResponseEntity<CustomerResponse> activate(
            @PathVariable("id") String customerId,
            Authentication authentication
    ) {
        var actorId = extractActorId(authentication);
        logger.info("Activating customer: {} by actor: {}", customerId, actorId.value());

        var result = activateCustomer.execute(CustomerId.of(customerId), actorId);

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

        logger.info("Customer activated: {}", customerId);
        return ResponseEntity.ok(CustomerResponse.from(result.unsafeGetValue()));
    }

    @PostMapping("/{id}/deactivate")
    @PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
    public ResponseEntity<CustomerResponse> deactivate(
            @PathVariable("id") String customerId,
            Authentication authentication
    ) {
        var actorId = extractActorId(authentication);
        logger.info("Deactivating customer: {} by actor: {}", customerId, actorId.value());

        var result = deactivateCustomer.execute(CustomerId.of(customerId), actorId);

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

        logger.info("Customer deactivated: {}", customerId);
        return ResponseEntity.ok(CustomerResponse.from(result.unsafeGetValue()));
    }

    @PostMapping("/{id}/delivery-addresses")
    @PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
    public ResponseEntity<CustomerResponse> addDeliveryAddress(
            @PathVariable("id") String customerId,
            @Valid @RequestBody AddDeliveryAddressRequest request,
            Authentication authentication
    ) {
        var actorId = extractActorId(authentication);
        logger.info("Adding delivery address to customer: {} by actor: {}", customerId, actorId.value());

        var cmd = new AddDeliveryAddressCommand(
                customerId, request.label(),
                request.street(), request.houseNumber(), request.postalCode(),
                request.city(), request.country(),
                request.contactPerson(), request.deliveryNotes()
        );
        var result = addDeliveryAddress.execute(cmd, actorId);

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

        logger.info("Delivery address added to customer: {}", customerId);
        return ResponseEntity.status(HttpStatus.CREATED).body(CustomerResponse.from(result.unsafeGetValue()));
    }

    @DeleteMapping("/{id}/delivery-addresses/{label}")
    @PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
    public ResponseEntity<Void> removeDeliveryAddress(
            @PathVariable("id") String customerId,
            @PathVariable("label") String label,
            Authentication authentication
    ) {
        var actorId = extractActorId(authentication);
        logger.info("Removing delivery address '{}' from customer: {} by actor: {}", label, customerId, actorId.value());

        var cmd = new RemoveDeliveryAddressCommand(customerId, label);
        var result = removeDeliveryAddress.execute(cmd, actorId);

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

        logger.info("Delivery address removed from customer: {}", customerId);
        return ResponseEntity.noContent().build();
    }

    @PutMapping("/{id}/frame-contract")
    @PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
    public ResponseEntity<CustomerResponse> setFrameContract(
            @PathVariable("id") String customerId,
            @Valid @RequestBody SetFrameContractRequest request,
            Authentication authentication
    ) {
        var actorId = extractActorId(authentication);
        logger.info("Setting frame contract for customer: {} by actor: {}", customerId, actorId.value());

        var lineItems = request.lineItems().stream()
                .map(li -> new SetFrameContractCommand.LineItem(
                        li.articleId(), li.agreedPrice(), li.agreedQuantity(), li.unit()))
                .collect(Collectors.toList());

        var cmd = new SetFrameContractCommand(
                customerId, request.validFrom(), request.validUntil(),
                request.rhythm(), lineItems
        );
        var result = setFrameContract.execute(cmd, actorId);

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

        logger.info("Frame contract set for customer: {}", customerId);
        return ResponseEntity.ok(CustomerResponse.from(result.unsafeGetValue()));
    }

    @DeleteMapping("/{id}/frame-contract")
    @PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
    public ResponseEntity<Void> removeFrameContract(
            @PathVariable("id") String customerId,
            Authentication authentication
    ) {
        var actorId = extractActorId(authentication);
        logger.info("Removing frame contract from customer: {} by actor: {}", customerId, actorId.value());

        var result = removeFrameContract.execute(CustomerId.of(customerId), actorId);

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

        logger.info("Frame contract removed from customer: {}", customerId);
        return ResponseEntity.noContent().build();
    }

    @PutMapping("/{id}/preferences")
    @PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
    public ResponseEntity<CustomerResponse> setPreferences(
            @PathVariable("id") String customerId,
            @Valid @RequestBody SetPreferencesRequest request,
            Authentication authentication
    ) {
        var actorId = extractActorId(authentication);
        logger.info("Setting preferences for customer: {} by actor: {}", customerId, actorId.value());

        var cmd = new SetPreferencesCommand(customerId, request.preferences());
        var result = setPreferences.execute(cmd, actorId);

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

        logger.info("Preferences set for customer: {}", customerId);
        return ResponseEntity.ok(CustomerResponse.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 CustomerDomainErrorException extends RuntimeException {
        private final CustomerError error;

        public CustomerDomainErrorException(CustomerError error) {
            super(error.message());
            this.error = error;
        }

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