package dev.lions.unionflow.server.security;

import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import io.smallrye.jwt.auth.principal.JWTParser;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.ForbiddenException;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.jboss.logging.Logger;

import java.util.Optional;
import java.util.UUID;

/**
 * Résout l'organisation active depuis le claim {@code organization} du JWT Keycloak 26.
 *
 * <p>Keycloak 26 Organizations injecte dans le token un claim de la forme :
 * <pre>
 * "organization": {
 *   "mutuelle-gbane": { "id": "uuid-kc-org", "name": "Mutuelle GBANE", "alias": "mutuelle-gbane" }
 * }
 * </pre>
 *
 * <p>Ce bean remplace progressivement {@link OrganisationContextFilter} (header-based).
 * Pendant la période de transition, le filtre header reste actif — ce resolver est
 * utilisé en complément par les endpoints qui lisent explicitement le claim JWT.
 *
 * <p>Un token scopé à une seule organization → résolution directe.
 * Un token multi-org sans scoping → exception (le client doit re-authentifier avec scoping).
 */
@ApplicationScoped
public class OrganisationContextResolver {

    private static final Logger LOG = Logger.getLogger(OrganisationContextResolver.class);

    @Inject
    JsonWebToken jwt;

    @Inject
    OrganisationRepository organisationRepository;

    /**
     * Résout l'UUID UnionFlow de l'organisation active depuis le claim JWT {@code organization}.
     *
     * @throws BadRequestException si le token est multi-org sans scoping ou si le claim manque
     * @throws ForbiddenException  si aucune organisation UnionFlow ne correspond au keycloak_org_id
     */
    public UUID resolveOrganisationId() {
        var orgClaim = jwt.<java.util.Map<String, Object>>getClaim("organization");

        if (orgClaim == null || orgClaim.isEmpty()) {
            throw new BadRequestException(
                    "Token JWT sans claim 'organization' — connectez-vous dans le contexte d'une organisation.");
        }

        if (orgClaim.size() > 1) {
            throw new BadRequestException(
                    "Token multi-organisation non scopé. Ré-authentifiez-vous avec l'organisation cible.");
        }

        // Single-org token : prendre la première (et seule) entrée
        var entry = orgClaim.entrySet().iterator().next().getValue();
        String kcOrgIdStr = extractId(entry);

        if (kcOrgIdStr == null) {
            LOG.warnf("Claim organization sans champ 'id' : %s", entry);
            throw new BadRequestException("Claim 'organization' malformé — champ 'id' manquant.");
        }

        UUID kcOrgId;
        try {
            kcOrgId = UUID.fromString(kcOrgIdStr);
        } catch (IllegalArgumentException e) {
            throw new BadRequestException("Claim organization.id n'est pas un UUID valide : " + kcOrgIdStr);
        }

        Optional<Organisation> orgOpt = organisationRepository
                .find("keycloakOrgId = ?1 AND actif = true", kcOrgId)
                .firstResultOptional();

        if (orgOpt.isEmpty()) {
            LOG.warnf("Aucune organisation UnionFlow avec keycloak_org_id=%s", kcOrgId);
            throw new ForbiddenException(
                    "Aucune organisation active trouvée pour cet identifiant Keycloak Organization.");
        }

        return orgOpt.get().getId();
    }

    /**
     * Variante qui retourne un {@code Optional} vide si le claim est absent
     * (pour les endpoints compatibles avec les deux modes header + JWT).
     */
    public Optional<UUID> resolveOrganisationIdIfPresent() {
        try {
            var orgClaim = jwt.<java.util.Map<String, Object>>getClaim("organization");
            if (orgClaim == null || orgClaim.isEmpty()) {
                return Optional.empty();
            }
            return Optional.of(resolveOrganisationId());
        } catch (BadRequestException | ForbiddenException e) {
            return Optional.empty();
        }
    }

    @SuppressWarnings("unchecked")
    private String extractId(Object entry) {
        if (entry instanceof java.util.Map) {
            Object id = ((java.util.Map<String, Object>) entry).get("id");
            return id != null ? id.toString() : null;
        }
        return null;
    }
}
