use super::*;
use tranquil_types::Nsid;

#[derive(Debug, Serialize)]
pub struct ScopeInfo {
    pub scope: String,
    pub category: String,
    pub required: bool,
    pub description: String,
    pub display_name: String,
    pub granted: Option<bool>,
    pub restricted: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub effective_scope: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct PermissionSetInfo {
    pub nsid: Nsid,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub aud: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub detail: Option<String>,
    pub include_scope: String,
    pub expanded: Vec<ScopeInfo>,
    pub granted: Option<bool>,
    pub restricted: bool,
}

#[derive(Debug, Serialize)]
pub struct FailedSetInfo {
    // `given_*` is the value as requested by the client.
    // Left as strings because the value from the client could be malformed.
    #[serde(rename = "nsid")]
    pub given_nsid: String,
    #[serde(rename = "aud", skip_serializing_if = "Option::is_none")]
    pub given_aud: Option<String>,
    pub reason: tranquil_scopes::ResolveFailure,
}

#[derive(Debug, Serialize)]
pub struct ConsentResponse {
    pub request_uri: String,
    pub client_id: ClientId,
    pub client_name: Option<String>,
    pub client_uri: Option<String>,
    pub logo_uri: Option<String>,
    pub scopes: Vec<ScopeInfo>,
    pub permission_sets: Vec<PermissionSetInfo>,
    pub failed_sets: Vec<FailedSetInfo>,
    pub show_consent: bool,
    pub did: Did,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub handle: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub is_delegation: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub controller_did: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub controller_handle: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub delegation_level: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct ConsentQuery {
    pub request_uri: String,
}

#[derive(Debug, Deserialize)]
pub struct ConsentSubmit {
    pub request_uri: String,
    pub approved_scopes: Vec<String>,
    pub remember: bool,
}

pub async fn consent_get(
    State(state): State<AppState>,
    Query(query): Query<ConsentQuery>,
) -> Response {
    let consent_request_id = RequestId::from(query.request_uri.clone());
    let request_data = match state
        .repos
        .oauth
        .get_authorization_request(&consent_request_id)
        .await
    {
        Ok(Some(data)) => data,
        Ok(None) => {
            return json_error(
                StatusCode::BAD_REQUEST,
                "invalid_request",
                "Invalid or expired request_uri",
            );
        }
        Err(e) => {
            return json_error(
                StatusCode::INTERNAL_SERVER_ERROR,
                "server_error",
                &format!("Database error: {:?}", e),
            );
        }
    };
    let flow_with_user = match AuthFlow::from_request_data(request_data.clone()) {
        Ok(flow) => match flow.require_user() {
            Ok(u) => u,
            Err(_) => {
                return json_error(StatusCode::FORBIDDEN, "access_denied", "Not authenticated");
            }
        },
        Err(_) => {
            return json_error(
                StatusCode::BAD_REQUEST,
                "expired_request",
                "Authorization request has expired",
            );
        }
    };

    let did = flow_with_user.did().clone();
    let client_cache = &state.client_metadata_cache;
    let client_metadata = client_cache
        .get(&request_data.parameters.client_id)
        .await
        .ok();
    let requested_scope_str = request_data
        .parameters
        .scope
        .as_deref()
        .filter(|s| !s.trim().is_empty())
        .unwrap_or("atproto");

    let controller_did_parsed: Option<Did> = request_data
        .controller_did
        .as_ref()
        .and_then(|s| s.parse().ok());
    let delegation_grant = if let Some(ref ctrl_did) = controller_did_parsed {
        state
            .repos
            .delegation
            .get_delegation(&did, ctrl_did)
            .await
            .ok()
            .flatten()
    } else {
        None
    };

    let authority = match delegation_grant.as_ref() {
        Some(grant) => scope_resolution::Authority::Delegated(&grant.granted_scopes),
        None => scope_resolution::Authority::FullSelf,
    };
    let effective =
        scope_resolution::resolve_effective_scopes(&*state.cache, requested_scope_str, authority)
            .await;
    let requested_scopes: Vec<&str> = effective.permitted.split_whitespace().collect();
    let preferences = state
        .repos
        .oauth
        .get_scope_preferences(&did, &request_data.parameters.client_id)
        .await
        .unwrap_or_default();
    let pref_map: std::collections::HashMap<_, _> = preferences
        .iter()
        .map(|p| (p.scope.as_str(), p.granted))
        .collect();
    let presented_item_strings: Vec<String> = effective
        .outcome
        .passthrough
        .iter()
        .cloned()
        .chain(effective.outcome.sets.iter().map(|g| match &g.aud {
            Some(a) => format!("include:{}?aud={}", g.nsid, a),
            None => format!("include:{}", g.nsid),
        }))
        .collect();
    let show_consent = should_show_consent(
        state.repos.oauth.as_ref(),
        &did,
        &request_data.parameters.client_id,
        &presented_item_strings,
    )
    .await
    .unwrap_or(true);
    let has_granular_scopes = requested_scopes.iter().any(|s| is_granular_scope(s));

    let grant_scope_str: Option<&str> =
        delegation_grant.as_ref().map(|g| g.granted_scopes.as_str());
    let coverage_of = |scope: &str| -> tranquil_pds::delegation::GrantCoverage {
        match grant_scope_str {
            Some(g) => tranquil_pds::delegation::grant_coverage(g, scope),
            None => tranquil_pds::delegation::GrantCoverage::Full,
        }
    };

    let make_scope_info = |scope: &str| -> ScopeInfo {
        let (restricted, effective_scope) = match coverage_of(scope) {
            tranquil_pds::delegation::GrantCoverage::Full => (false, None),
            tranquil_pds::delegation::GrantCoverage::Narrowed(narrowed) => (false, Some(narrowed)),
            tranquil_pds::delegation::GrantCoverage::Withheld => (true, None),
        };
        let described = effective_scope.as_deref().unwrap_or(scope);
        let (category, required, description, display_name) =
            if let Some(def) = tranquil_pds::oauth::scopes::SCOPE_DEFINITIONS.get(described) {
                let desc = if described == "atproto" && has_granular_scopes {
                    "AT Protocol baseline scope (permissions determined by selected options below)"
                        .to_string()
                } else {
                    def.description.to_string()
                };
                let name = if described == "atproto" && has_granular_scopes {
                    "AT Protocol Access".to_string()
                } else {
                    def.display_name.to_string()
                };
                (
                    def.category.display_name().to_string(),
                    def.required,
                    desc,
                    name,
                )
            } else if described.starts_with("ref:") {
                (
                    "Reference".to_string(),
                    false,
                    "Referenced scope".to_string(),
                    described.to_string(),
                )
            } else {
                (
                    "Other".to_string(),
                    false,
                    format!("Access to {}", described),
                    described.to_string(),
                )
            };
        let granted = pref_map.get(scope).copied();
        ScopeInfo {
            scope: scope.to_string(),
            category,
            required,
            description,
            display_name,
            granted,
            restricted,
            effective_scope,
        }
    };

    let scopes: Vec<ScopeInfo> = effective
        .outcome
        .passthrough
        .iter()
        .map(|s| make_scope_info(s))
        .collect();

    let permission_sets: Vec<PermissionSetInfo> = effective
        .outcome
        .sets
        .iter()
        .map(|g| {
            let include_scope = match &g.aud {
                Some(a) => format!("include:{}?aud={}", g.nsid, a),
                None => format!("include:{}", g.nsid),
            };
            let expanded: Vec<ScopeInfo> = g.expanded.iter().map(|s| make_scope_info(s)).collect();
            let restricted = !expanded.is_empty() && expanded.iter().all(|s| s.restricted);
            PermissionSetInfo {
                nsid: g.nsid.clone(),
                aud: g.aud.clone(),
                title: g.title.clone(),
                detail: g.detail.clone(),
                granted: pref_map.get(include_scope.as_str()).copied(),
                include_scope,
                expanded,
                restricted,
            }
        })
        .collect();

    let failed_sets: Vec<FailedSetInfo> = effective
        .outcome
        .failures
        .iter()
        .map(|f| FailedSetInfo {
            given_nsid: f.given_nsid.clone(),
            given_aud: f.given_aud.clone(),
            reason: f.reason.clone(),
        })
        .collect();

    let account_handle = state
        .repos
        .user
        .get_handle_by_did(&did)
        .await
        .ok()
        .flatten()
        .map(|h| h.to_string());

    let (is_delegation, controller_did_resp, controller_handle, delegation_level) =
        if let Some(ref ctrl_did) = controller_did_parsed {
            let ctrl_handle = state
                .repos
                .user
                .get_handle_by_did(ctrl_did)
                .await
                .ok()
                .flatten()
                .map(|h| h.to_string());

            let level = if let Some(ref grant) = delegation_grant {
                let preset = tranquil_pds::delegation::SCOPE_PRESETS
                    .iter()
                    .find(|p| p.scopes == grant.granted_scopes.as_str());
                preset
                    .map(|p| p.label.to_string())
                    .unwrap_or_else(|| "Custom".to_string())
            } else {
                "Unknown".to_string()
            };

            (
                Some(true),
                Some(ctrl_did.to_string()),
                ctrl_handle,
                Some(level),
            )
        } else {
            (None, None, None, None)
        };

    Json(ConsentResponse {
        request_uri: query.request_uri.clone(),
        client_id: request_data.parameters.client_id.clone(),
        client_name: client_metadata.as_ref().and_then(|m| m.client_name.clone()),
        client_uri: client_metadata.as_ref().and_then(|m| m.client_uri.clone()),
        logo_uri: client_metadata.as_ref().and_then(|m| m.logo_uri.clone()),
        scopes,
        permission_sets,
        failed_sets,
        show_consent,
        did: did.clone(),
        handle: account_handle,
        is_delegation,
        controller_did: controller_did_resp,
        controller_handle,
        delegation_level,
    })
    .into_response()
}

pub async fn consent_post(
    State(state): State<AppState>,
    Json(form): Json<ConsentSubmit>,
) -> Response {
    tracing::info!(
        "consent_post: approved_scopes={:?}, remember={}",
        form.approved_scopes,
        form.remember
    );
    let consent_post_request_id = RequestId::from(form.request_uri.clone());
    let request_data = match state
        .repos
        .oauth
        .get_authorization_request(&consent_post_request_id)
        .await
    {
        Ok(Some(data)) => data,
        Ok(None) => {
            return json_error(
                StatusCode::BAD_REQUEST,
                "invalid_request",
                "Invalid or expired request_uri",
            );
        }
        Err(e) => {
            return json_error(
                StatusCode::INTERNAL_SERVER_ERROR,
                "server_error",
                &format!("Database error: {:?}", e),
            );
        }
    };
    let flow_with_user = match AuthFlow::from_request_data(request_data.clone()) {
        Ok(flow) => match flow.require_user() {
            Ok(u) => u,
            Err(_) => {
                return json_error(StatusCode::FORBIDDEN, "access_denied", "Not authenticated");
            }
        },
        Err(_) => {
            let _ = state
                .repos
                .oauth
                .delete_authorization_request(&consent_post_request_id)
                .await;
            return json_error(
                StatusCode::BAD_REQUEST,
                "invalid_request",
                "Authorization request has expired",
            );
        }
    };

    let did = flow_with_user.did().clone();
    let original_scope_str = request_data
        .parameters
        .scope
        .as_deref()
        .unwrap_or("atproto");

    let controller_did_parsed: Option<Did> = request_data
        .controller_did
        .as_ref()
        .and_then(|s| s.parse().ok());

    let delegation_grant = match controller_did_parsed.as_ref() {
        Some(ctrl_did) => state
            .repos
            .delegation
            .get_delegation(&did, ctrl_did)
            .await
            .ok()
            .flatten(),
        None => None,
    };

    let authority = match delegation_grant.as_ref() {
        Some(grant) => scope_resolution::Authority::Delegated(&grant.granted_scopes),
        None => scope_resolution::Authority::FullSelf,
    };
    let effective =
        scope_resolution::resolve_effective_scopes(&*state.cache, original_scope_str, authority)
            .await;
    let include_token = |nsid: &str, aud: &Option<String>| -> String {
        match aud {
            Some(a) => format!("include:{}?aud={}", nsid, a),
            None => format!("include:{}", nsid),
        }
    };
    let approved_failed_sets: Vec<String> = effective
        .outcome
        .failures
        .iter()
        .filter(|f| {
            form.approved_scopes
                .contains(&include_token(&f.given_nsid, &f.given_aud))
        })
        .map(|f| f.given_nsid.clone())
        .collect();
    if !approved_failed_sets.is_empty() {
        return json_error(
            StatusCode::BAD_REQUEST,
            "invalid_scope",
            &format!(
                "Could not resolve approved permission set(s): {}",
                approved_failed_sets.join(", ")
            ),
        );
    }
    let presented_items: Vec<String> = effective
        .outcome
        .passthrough
        .iter()
        .cloned()
        .chain(
            effective
                .outcome
                .sets
                .iter()
                .map(|g| include_token(&g.nsid, &g.aud)),
        )
        .collect();
    let atproto_was_requested = presented_items.iter().any(|s| s == "atproto");
    if atproto_was_requested && !form.approved_scopes.contains(&"atproto".to_string()) {
        return json_error(
            StatusCode::BAD_REQUEST,
            "invalid_request",
            "The atproto scope was requested and must be approved",
        );
    }
    let mut final_approved: Vec<String> = form.approved_scopes.clone();
    final_approved.retain(|s| presented_items.iter().any(|p| p == s) || s == "atproto");
    if final_approved.is_empty() {
        return json_error(
            StatusCode::BAD_REQUEST,
            "invalid_request",
            "At least one scope must be approved",
        );
    }
    let approved_scope_str = final_approved.join(" ");
    let has_valid_scope = final_approved.iter().all(|s| is_valid_scope(s));
    if !has_valid_scope {
        return json_error(
            StatusCode::BAD_REQUEST,
            "invalid_request",
            "Invalid scope format",
        );
    }
    if form.remember {
        let preferences: Vec<ScopePreference> = presented_items
            .iter()
            .map(|s| ScopePreference {
                scope: s.clone(),
                granted: form.approved_scopes.contains(s),
            })
            .collect();
        let _ = state
            .repos
            .oauth
            .upsert_scope_preferences(&did, &request_data.parameters.client_id, &preferences)
            .await;
    }
    if let Err(e) = state
        .repos
        .oauth
        .update_request_scope(&consent_post_request_id, &approved_scope_str)
        .await
    {
        tracing::warn!("Failed to update request scope: {:?}", e);
    }
    let code = AuthorizationCode::generate();
    if state
        .repos
        .oauth
        .update_authorization_request(
            &consent_post_request_id,
            &did,
            request_data.device_id.as_ref(),
            &code,
        )
        .await
        .is_err()
    {
        return json_error(
            StatusCode::INTERNAL_SERVER_ERROR,
            "server_error",
            "Failed to complete authorization",
        );
    }
    let redirect_uri = &request_data.parameters.redirect_uri;
    let intermediate_url = build_intermediate_redirect_url(
        redirect_uri,
        code.as_str(),
        request_data.parameters.state.as_deref(),
        request_data.parameters.response_mode.map(|m| m.as_str()),
    );
    tracing::info!(
        intermediate_url = %intermediate_url,
        client_redirect = %redirect_uri,
        "consent_post returning JSON with intermediate URL (for 303 redirect)"
    );
    Json(serde_json::json!({ "redirect_uri": intermediate_url })).into_response()
}

#[derive(Debug, Deserialize)]
pub struct RenewRequest {
    pub request_uri: String,
}

pub async fn authorize_renew(
    State(state): State<AppState>,
    _rate_limit: OAuthRateLimited<OAuthAuthorizeLimit>,
    Json(form): Json<RenewRequest>,
) -> Response {
    let request_id = RequestId::from(form.request_uri.clone());
    let request_data = match state
        .repos
        .oauth
        .get_authorization_request(&request_id)
        .await
    {
        Ok(Some(data)) => data,
        Ok(None) => {
            return json_error(
                StatusCode::BAD_REQUEST,
                "invalid_request",
                "Unknown authorization request",
            );
        }
        Err(_) => {
            return json_error(
                StatusCode::INTERNAL_SERVER_ERROR,
                "server_error",
                "Database error",
            );
        }
    };

    if request_data.did.is_none() {
        return json_error(
            StatusCode::BAD_REQUEST,
            "invalid_request",
            "Authorization request not yet authenticated",
        );
    }

    let now = Utc::now();
    if request_data.expires_at >= now {
        return Json(serde_json::json!({
            "request_uri": form.request_uri,
            "renewed": false
        }))
        .into_response();
    }

    let staleness = now - request_data.expires_at;
    if staleness.num_seconds() > MAX_RENEWAL_STALENESS_SECONDS {
        let _ = state
            .repos
            .oauth
            .delete_authorization_request(&request_id)
            .await;
        return json_error(
            StatusCode::BAD_REQUEST,
            "invalid_request",
            "Authorization request expired too long ago to renew",
        );
    }

    let new_expires_at = now + chrono::Duration::seconds(RENEW_EXPIRY_SECONDS);
    match state
        .repos
        .oauth
        .extend_authorization_request_expiry(&request_id, new_expires_at)
        .await
    {
        Ok(true) => Json(serde_json::json!({
            "request_uri": form.request_uri,
            "renewed": true
        }))
        .into_response(),
        Ok(false) => json_error(
            StatusCode::BAD_REQUEST,
            "invalid_request",
            "Authorization request could not be renewed",
        ),
        Err(_) => json_error(
            StatusCode::INTERNAL_SERVER_ERROR,
            "server_error",
            "Database error",
        ),
    }
}
