use askama::Template;
use askama_web::WebTemplate;
use axum::extract::State;
use axum::http::HeaderMap;
use axum::response::{IntoResponse, Redirect, Response};

use crate::auth_guard::AuthUser;
use crate::flash;
use crate::htmx;
use crate::page::PageContext;
use crate::routes::profile::update_keycloak_user_attributes;
use crate::state::AppState;
use crate::stripe_util::{StripeEvent, verify_webhook_signature};

#[derive(Template, WebTemplate)]
#[template(path = "billing.html")]
pub struct BillingTemplate {
    pub page: PageContext,
    pub plan: String,
    pub is_pro: bool,
}

pub async fn index(
    State(state): State<AppState>,
    headers: HeaderMap,
    AuthUser(user): AuthUser,
) -> impl IntoResponse {
    let plan = user.plan.unwrap_or_else(|| "free".to_string());
    let is_pro = plan == "pro";

    let page = PageContext::from_request(&headers, &state.announcement);
    let clear = page.flash.is_some();

    let template = BillingTemplate { page, plan, is_pro };

    if clear {
        (axum::response::AppendHeaders([flash::clear_flash()]), template).into_response()
    } else {
        template.into_response()
    }
}

pub async fn checkout(
    State(state): State<AppState>,
    headers: HeaderMap,
    AuthUser(user): AuthUser,
) -> Response {
    let mut params = stripe::CreateCheckoutSession::new();
    params.mode = Some(stripe::CheckoutSessionMode::Subscription);
    params.success_url = Some("https://my.irc.now/billing?success=1");
    params.cancel_url = Some("https://my.irc.now/billing");
    params.client_reference_id = Some(&user.sub);
    params.line_items = Some(vec![stripe::CreateCheckoutSessionLineItems {
        quantity: Some(1),
        price: Some(state.stripe_price_id.clone()),
        ..Default::default()
    }]);

    if let Some(ref cid) = user.stripe_customer_id {
        if let Ok(customer_id) = cid.parse() {
            params.customer = Some(customer_id);
        }
    }

    let mut metadata = std::collections::HashMap::new();
    metadata.insert("keycloak_sub".to_string(), user.sub.clone());
    params.metadata = Some(metadata);

    let session = match stripe::CheckoutSession::create(&state.stripe_client, params).await {
        Ok(s) => s,
        Err(e) => {
            tracing::error!("stripe checkout session creation failed: {e}");
            if htmx::is_htmx(&headers) {
                return ([htmx::hx_redirect("/billing")], "").into_response();
            }
            return Redirect::to("/billing").into_response();
        }
    };

    let url = match session.url {
        Some(u) => u,
        None => {
            tracing::error!("stripe checkout session has no url");
            if htmx::is_htmx(&headers) {
                return ([htmx::hx_redirect("/billing")], "").into_response();
            }
            return Redirect::to("/billing").into_response();
        }
    };

    if htmx::is_htmx(&headers) {
        ([htmx::hx_redirect(&url)], "").into_response()
    } else {
        Redirect::to(&url).into_response()
    }
}

pub async fn portal(
    State(state): State<AppState>,
    AuthUser(user): AuthUser,
) -> Result<Redirect, Redirect> {
    let cid = user.stripe_customer_id.ok_or_else(|| {
        Redirect::to("/billing")
    })?;

    let customer_id: stripe::CustomerId = cid.parse().map_err(|_| {
        Redirect::to("/billing")
    })?;

    let mut params = stripe::CreateBillingPortalSession::new(customer_id);
    params.return_url = Some("https://my.irc.now/billing");

    let session = stripe::BillingPortalSession::create(&state.stripe_client, params)
        .await
        .map_err(|e| {
            tracing::error!("stripe portal session creation failed: {e}");
            Redirect::to("/billing")
        })?;

    Ok(Redirect::to(&session.url))
}

pub async fn webhook(
    State(state): State<AppState>,
    headers: HeaderMap,
    body: String,
) -> &'static str {
    let signature = headers
        .get("stripe-signature")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("");

    if !verify_webhook_signature(&body, signature, &state.stripe_webhook_secret) {
        tracing::warn!("invalid stripe webhook signature");
        return "invalid signature";
    }

    let event: StripeEvent = match serde_json::from_str(&body) {
        Ok(e) => e,
        Err(e) => {
            tracing::error!("failed to parse stripe event: {e}");
            return "parse error";
        }
    };

    match event.event_type.as_str() {
        "checkout.session.completed" => {
            let customer = event.data.object.get("customer")
                .and_then(|v| v.as_str());
            let sub = event.data.object.get("metadata")
                .and_then(|m| m.get("keycloak_sub"))
                .and_then(|v| v.as_str());

            if let (Some(customer), Some(sub)) = (customer, sub) {
                let result = sqlx::query(
                    "UPDATE users SET plan = 'pro', stripe_customer_id = $1 WHERE keycloak_sub = $2",
                )
                .bind(customer)
                .bind(sub)
                .execute(&state.db)
                .await;

                match result {
                    Ok(_) => {
                        if let Err(e) = update_keycloak_user_attributes(&state, sub, "pro", true).await {
                            tracing::warn!("keycloak attribute sync after upgrade failed: {e}");
                        }
                        crate::events::record(
                            &state.db, sub, "plan_upgrade",
                            Some(serde_json::json!({"from": "free", "to": "pro"})),
                        ).await;
                    }
                    Err(e) => tracing::error!("failed to upgrade user {sub}: {e}"),
                }
            }
        }
        "customer.subscription.deleted" => {
            let customer = event.data.object.get("customer")
                .and_then(|v| v.as_str());

            if let Some(customer) = customer {
                let sub: Option<String> = sqlx::query_scalar(
                    "UPDATE users SET plan = 'free' WHERE stripe_customer_id = $1 RETURNING keycloak_sub",
                )
                .bind(customer)
                .fetch_optional(&state.db)
                .await
                .unwrap_or(None);

                if let Some(sub) = sub {
                    if let Err(e) = update_keycloak_user_attributes(&state, &sub, "free", true).await {
                        tracing::warn!("keycloak attribute sync after downgrade failed: {e}");
                    }
                    crate::events::record(
                        &state.db, &sub, "plan_downgrade",
                        Some(serde_json::json!({"from": "pro", "to": "free"})),
                    ).await;
                }
            }
        }
        _ => {}
    }

    "ok"
}
