use super::fetch_profile_from_request_token;
use crate::middleware::fetch_profile_from_request_connection_string;

use actix_web::HttpRequest;
use awc::ClientRequest;
use myc_core::domain::dtos::security_group::PermissionedRole;
use myc_http_tools::{
    functions::compress_and_encode_profile_to_base64,
    responses::GatewayError,
    settings::{DEFAULT_CONNECTION_STRING_KEY, DEFAULT_PROFILE_KEY},
    Profile,
};
use opentelemetry::{global, KeyValue};
use reqwest::header::{HeaderName, HeaderValue};
use std::str::FromStr;
use tracing::Instrument;
use uuid::Uuid;

/// Fetch profile from email and inject on client request
///
/// Try to extract profile from email (these extracted from the bearer token)
/// and, then find the profile from the email and inject profile into the
/// forward request.
///
/// These use-case is usual over middleware or routers parts of the application.
#[tracing::instrument(
    name = "fetch_and_inject_profile_from_token_to_forward", 
    skip_all,
    fields(
        //
        // User information
        //
        myc.router.profile_id = tracing::field::Empty,
        myc.router.is_staff = tracing::field::Empty,
        myc.router.is_manager = tracing::field::Empty,
        myc.router.is_subscription = tracing::field::Empty,
        myc.router.has_tenant_ownership = tracing::field::Empty,
        myc.router.has_licensed_resources = tracing::field::Empty,
    )
)]
pub async fn fetch_and_inject_profile_from_token_to_forward(
    req: HttpRequest,
    mut forwarded_req: ClientRequest,
    tenant: Option<Uuid>,
    roles: Option<Vec<PermissionedRole>>,
    service_name: String,
) -> Result<(ClientRequest, Profile), GatewayError> {
    let span = tracing::Span::current();

    tracing::info!(
        stage = "router.profile_injection",
        "Starting profile injection to downstream"
    );

    let profile = if req.headers().get(DEFAULT_CONNECTION_STRING_KEY).is_some()
    {
        //
        // Try to fetch profile from connection string
        //
        fetch_profile_from_request_connection_string(
            req,
            None,
            roles.to_owned(),
        )
        .instrument(span.to_owned())
        .await?
    } else {
        //
        // Try to fetch profile from token
        //
        fetch_profile_from_request_token(req, tenant, roles.to_owned())
            .instrument(span.to_owned())
            .await?
    };

    tracing::debug!("Profile: {:?}", profile);

    span.record("myc.router.profile_id", &Some(profile.acc_id.to_string()))
        .record("myc.router.is_staff", &Some(profile.is_staff))
        .record("myc.router.is_manager", &Some(profile.is_manager))
        .record("myc.router.is_subscription", &Some(profile.is_subscription))
        .record(
            "myc.router.has_tenant_ownership",
            &Some(profile.tenants_ownership.is_some()),
        )
        .record(
            "myc.router.has_licensed_resources",
            &Some(profile.licensed_resources.is_some()),
        );

    if let Some(_) = roles {
        if profile.licensed_resources.is_none()
            && (!profile.is_manager && !profile.is_staff)
        {
            return Err(GatewayError::Forbidden(
                "User does not have permission to perform this action"
                    .to_string(),
            ));
        }
    }

    // Get a meter
    let meter = global::meter("router_counter");

    // Create a metric
    let counter = meter.u64_counter("router.requests_count").build();

    counter.add(
        1,
        &[
            KeyValue::new(
                "identifier",
                format!(
                    "profileId:{}",
                    profile.acc_id.to_string().replace("-", "")
                ),
            ),
            KeyValue::new("down_service_name", service_name),
        ],
    );

    forwarded_req.headers_mut().insert(
        HeaderName::from_str(DEFAULT_PROFILE_KEY).unwrap(),
        HeaderValue::from_str(&compress_and_encode_profile_to_base64(
            profile.to_profile(),
        )?)
        .unwrap(),
    );

    tracing::info!(
        stage = "router.profile_injection",
        outcome = "ok",
        "Profile injected into downstream request"
    );

    Ok((forwarded_req, profile.to_profile()))
}
