package com.cameleer.server.app.controller;

import com.cameleer.server.app.dto.ErrorResponse;
import com.cameleer.server.app.dto.OidcAdminConfigRequest;
import com.cameleer.server.app.dto.OidcAdminConfigResponse;
import com.cameleer.server.app.dto.OidcTestResult;
import com.cameleer.server.app.security.OidcTokenExchanger;
import com.cameleer.server.core.admin.AuditCategory;
import com.cameleer.server.core.admin.AuditResult;
import com.cameleer.server.core.admin.AuditService;
import com.cameleer.server.core.security.OidcConfig;
import com.cameleer.server.core.security.OidcConfigRepository;
import jakarta.servlet.http.HttpServletRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
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.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * Admin endpoints for managing OIDC provider configuration.
 * Protected by {@code ROLE_ADMIN} via SecurityConfig URL patterns ({@code /api/v1/admin/**}).
 */
@RestController
@RequestMapping("/api/v1/admin/oidc")
@Tag(name = "OIDC Config Admin", description = "OIDC provider configuration (ADMIN only)")
@PreAuthorize("hasRole('ADMIN')")
public class OidcConfigAdminController {

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

    private final OidcConfigRepository configRepository;
    private final OidcTokenExchanger tokenExchanger;
    private final AuditService auditService;

    public OidcConfigAdminController(OidcConfigRepository configRepository,
                                      OidcTokenExchanger tokenExchanger,
                                      AuditService auditService) {
        this.configRepository = configRepository;
        this.tokenExchanger = tokenExchanger;
        this.auditService = auditService;
    }

    @GetMapping
    @Operation(summary = "Get OIDC configuration")
    @ApiResponse(responseCode = "200", description = "Current OIDC configuration (client_secret masked)")
    public ResponseEntity<OidcAdminConfigResponse> getConfig(HttpServletRequest httpRequest) {
        auditService.log("view_oidc_config", AuditCategory.CONFIG, null, null, AuditResult.SUCCESS, httpRequest);
        Optional<OidcConfig> config = configRepository.find();
        if (config.isEmpty()) {
            return ResponseEntity.ok(OidcAdminConfigResponse.unconfigured());
        }
        return ResponseEntity.ok(OidcAdminConfigResponse.from(config.get()));
    }

    @PutMapping
    @Operation(summary = "Save OIDC configuration")
    @ApiResponse(responseCode = "200", description = "Configuration saved")
    @ApiResponse(responseCode = "400", description = "Invalid configuration",
            content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
    public ResponseEntity<OidcAdminConfigResponse> saveConfig(@RequestBody OidcAdminConfigRequest request,
                                                                HttpServletRequest httpRequest) {
        // Resolve client_secret: if masked or empty, preserve existing
        String clientSecret = request.clientSecret();
        if (clientSecret == null || clientSecret.isBlank() || clientSecret.equals("********")) {
            Optional<OidcConfig> existing = configRepository.find();
            clientSecret = existing.map(OidcConfig::clientSecret).orElse("");
        }

        if (request.enabled() && (request.issuerUri() == null || request.issuerUri().isBlank())) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
                    "issuerUri is required when OIDC is enabled");
        }
        if (request.enabled() && (request.clientId() == null || request.clientId().isBlank())) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
                    "clientId is required when OIDC is enabled");
        }

        OidcConfig config = new OidcConfig(
                request.enabled(),
                request.issuerUri() != null ? request.issuerUri() : "",
                request.clientId() != null ? request.clientId() : "",
                clientSecret,
                request.rolesClaim() != null ? request.rolesClaim() : "roles",
                request.defaultRoles() != null ? request.defaultRoles() : List.of("VIEWER"),
                request.autoSignup(),
                request.displayNameClaim() != null ? request.displayNameClaim() : "name",
                request.userIdClaim() != null ? request.userIdClaim() : "sub",
                request.audience() != null ? request.audience() : "",
                request.additionalScopes() != null ? request.additionalScopes() : List.of()
        );

        configRepository.save(config);
        tokenExchanger.invalidateCache();

        auditService.log("update_oidc", AuditCategory.CONFIG, "oidc", Map.of(), AuditResult.SUCCESS, httpRequest);
        log.info("OIDC configuration updated: enabled={}, issuer={}", config.enabled(), config.issuerUri());
        return ResponseEntity.ok(OidcAdminConfigResponse.from(config));
    }

    @PostMapping("/test")
    @Operation(summary = "Test OIDC provider connectivity")
    @ApiResponse(responseCode = "200", description = "Provider reachable")
    @ApiResponse(responseCode = "400", description = "Provider unreachable or misconfigured",
            content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
    public ResponseEntity<OidcTestResult> testConnection(HttpServletRequest httpRequest) {
        Optional<OidcConfig> config = configRepository.find();
        if (config.isEmpty() || !config.get().enabled()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
                    "OIDC is not configured or disabled");
        }

        try {
            tokenExchanger.invalidateCache();
            String authEndpoint = tokenExchanger.getAuthorizationEndpoint();
            auditService.log("test_oidc", AuditCategory.CONFIG, "oidc", null, AuditResult.SUCCESS, httpRequest);
            return ResponseEntity.ok(new OidcTestResult("ok", authEndpoint));
        } catch (Exception e) {
            log.warn("OIDC connectivity test failed: {}", e.getMessage());
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
                    "Failed to reach OIDC provider: " + e.getMessage());
        }
    }

    @DeleteMapping
    @Operation(summary = "Delete OIDC configuration")
    @ApiResponse(responseCode = "204", description = "Configuration deleted")
    public ResponseEntity<Void> deleteConfig(HttpServletRequest httpRequest) {
        configRepository.delete();
        tokenExchanger.invalidateCache();
        auditService.log("delete_oidc", AuditCategory.CONFIG, "oidc", null, AuditResult.SUCCESS, httpRequest);
        log.info("OIDC configuration deleted");
        return ResponseEntity.noContent().build();
    }
}
