package net.siegeln.cameleer.saas.portal;

import net.siegeln.cameleer.saas.config.TenantContext;
import net.siegeln.cameleer.saas.identity.LogtoManagementClient;
import net.siegeln.cameleer.saas.tenant.TenantEntity;
import net.siegeln.cameleer.saas.tenant.TenantService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.http.HttpStatus;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

@Service
public class TenantSsoService {

    private static final Logger log = LoggerFactory.getLogger(TenantSsoService.class);

    private final LogtoManagementClient logtoClient;
    private final TenantService tenantService;

    public TenantSsoService(LogtoManagementClient logtoClient, TenantService tenantService) {
        this.logtoClient = logtoClient;
        this.tenantService = tenantService;
    }

    /** List SSO connectors linked to the current tenant's organization. */
    @SuppressWarnings("unchecked")
    public List<Map<String, Object>> listConnectors() {
        String orgId = resolveOrgId();
        List<Map<String, Object>> jitConnectors = logtoClient.getOrgJitSsoConnectors(orgId);
        Set<String> linkedIds = jitConnectors.stream()
            .map(c -> String.valueOf(c.get("id")))
            .collect(Collectors.toSet());

        if (linkedIds.isEmpty()) return List.of();

        // Enrich with full connector details
        List<Map<String, Object>> allConnectors = logtoClient.listSsoConnectors();
        return allConnectors.stream()
            .filter(c -> linkedIds.contains(String.valueOf(c.get("id"))))
            .toList();
    }

    /** Create an SSO connector and link it to the tenant's organization. */
    public Map<String, Object> createConnector(String providerName, String connectorName,
                                                Map<String, Object> config, List<String> domains) {
        String orgId = resolveOrgId();
        var connector = logtoClient.createSsoConnector(providerName, connectorName, config, domains);
        if (connector == null) {
            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to create SSO connector");
        }
        String connectorId = String.valueOf(connector.get("id"));
        logtoClient.linkSsoConnectorToOrg(orgId, connectorId);
        log.info("Created SSO connector '{}' ({}) and linked to org {}", connectorName, connectorId, orgId);
        return connector;
    }

    /** Get a single SSO connector (validates it belongs to this tenant). */
    public Map<String, Object> getConnector(String connectorId) {
        validateConnectorBelongsToTenant(connectorId);
        return logtoClient.getSsoConnector(connectorId);
    }

    /** Update an SSO connector (validates it belongs to this tenant). */
    public Map<String, Object> updateConnector(String connectorId, Map<String, Object> updates) {
        validateConnectorBelongsToTenant(connectorId);
        return logtoClient.updateSsoConnector(connectorId, updates);
    }

    /** Delete an SSO connector (unlinks from org and deletes). */
    public void deleteConnector(String connectorId) {
        String orgId = resolveOrgId();
        validateConnectorBelongsToTenant(connectorId);
        logtoClient.unlinkSsoConnectorFromOrg(orgId, connectorId);
        logtoClient.deleteSsoConnector(connectorId);
        log.info("Deleted SSO connector {} from org {}", connectorId, orgId);
    }

    /** Test an SSO connector by fetching its details (validates provider metadata). */
    public Map<String, Object> testConnector(String connectorId) {
        validateConnectorBelongsToTenant(connectorId);
        var connector = logtoClient.getSsoConnector(connectorId);
        if (connector == null) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Connector not found");
        }
        // Logto resolves providerConfig (OIDC discovery / SAML metadata) when fetching.
        // If providerConfig is present and non-empty, the IdP is reachable.
        @SuppressWarnings("unchecked")
        var providerConfig = (Map<String, Object>) connector.get("providerConfig");
        boolean reachable = providerConfig != null && !providerConfig.isEmpty();
        return Map.of(
            "status", reachable ? "ok" : "unreachable",
            "providerName", String.valueOf(connector.get("providerName")),
            "connectorName", String.valueOf(connector.get("connectorName"))
        );
    }

    private String resolveOrgId() {
        UUID tenantId = TenantContext.getTenantId();
        TenantEntity tenant = tenantService.getById(tenantId)
            .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Tenant not found"));
        String orgId = tenant.getLogtoOrgId();
        if (orgId == null || orgId.isBlank()) {
            throw new ResponseStatusException(HttpStatus.PRECONDITION_FAILED, "Tenant has no Logto organization");
        }
        return orgId;
    }

    private void validateConnectorBelongsToTenant(String connectorId) {
        String orgId = resolveOrgId();
        List<Map<String, Object>> jitConnectors = logtoClient.getOrgJitSsoConnectors(orgId);
        boolean linked = jitConnectors.stream()
            .anyMatch(c -> connectorId.equals(String.valueOf(c.get("id"))));
        if (!linked) {
            throw new ResponseStatusException(HttpStatus.FORBIDDEN, "SSO connector does not belong to this tenant");
        }
    }
}
