package de.effigenix.infrastructure.security;

import de.effigenix.domain.usermanagement.Permission;
import de.effigenix.shared.security.Action;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.security.AuthorizationPort;
import de.effigenix.shared.security.BranchId;
import de.effigenix.shared.security.ResourceId;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Spring Security implementation of AuthorizationPort.
 *
 * This is the Anti-Corruption Layer (ACL) between domain logic and User Management.
 * It adapts Spring Security's authentication/authorization model to the domain's
 * type-safe, action-oriented authorization.
 *
 * The ActorId parameter makes the port framework-agnostic. This adapter still uses
 * Spring SecurityContext internally (MVP), but the interface contract is explicit.
 *
 * Authorization Flow:
 * 1. Controller extracts ActorId from Authentication
 * 2. Use Case calls: authPort.can(actorId, ProductionAction.RECIPE_WRITE)
 * 3. Adapter maps: ProductionAction.RECIPE_WRITE → Permission.RECIPE_WRITE
 * 4. Adapter checks: Does current user have Permission.RECIPE_WRITE?
 *
 * Infrastructure Layer → Implements Shared Kernel interface
 */
@Component
public class SpringSecurityAuthorizationAdapter implements AuthorizationPort {

    private final ActionToPermissionMapper actionMapper;

    public SpringSecurityAuthorizationAdapter(ActionToPermissionMapper actionMapper) {
        this.actionMapper = actionMapper;
    }

    @Override
    public boolean can(ActorId actor, Action action) {
        if (actor == null || action == null) {
            return false;
        }

        Permission requiredPermission = actionMapper.mapActionToPermission(action);
        Set<Permission> userPermissions = getCurrentUserPermissions();

        return userPermissions.contains(requiredPermission);
    }

    @Override
    public boolean can(ActorId actor, Action action, ResourceId resource) {
        if (!can(actor, action)) {
            return false;
        }

        if (resource instanceof BranchId targetBranch) {
            Optional<BranchId> userBranch = getCurrentUserBranch();

            // If user has no branch assignment, they have global access (admin)
            if (userBranch.isEmpty()) {
                return true;
            }

            // User can only access resources in their assigned branch
            return userBranch.get().equals(targetBranch);
        }

        return true;
    }

    private Set<Permission> getCurrentUserPermissions() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication == null || !authentication.isAuthenticated()) {
            return Set.of();
        }

        return authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .map(Permission::valueOf)
                .collect(Collectors.toSet());
    }

    private Optional<BranchId> getCurrentUserBranch() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication == null || !authentication.isAuthenticated()) {
            return Optional.empty();
        }

        if (authentication.getDetails() instanceof JwtAuthenticationDetails details) {
            String branchId = details.getBranchId();
            return branchId != null ? Optional.of(BranchId.of(branchId)) : Optional.empty();
        }

        return Optional.empty();
    }
}
