// retrigger-build-marker-2
#![allow(dead_code)]

// Gateway service - routes requests to upstream services
use axum::{
    body::Body,
    extract::{Request, State},
    http::{HeaderValue, Method, StatusCode},
    response::IntoResponse,
    routing::any,
    Router,
};
use std::net::SocketAddr;
use tower_http::cors::{AllowHeaders, AllowOrigin, CorsLayer};
use tower_http::set_header::SetResponseHeaderLayer;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

#[derive(Clone)]
struct Services {
    users_url: String,
    companies_url: String,
    jobs_url: String,
    job_seekers_url: String,
    customers_url: String,
    // ── 9 separate profession services ────────────────────────────────────
    photographers_url: String,
    makeup_artists_url: String,
    tutors_url: String,
    developers_url: String,
    video_editors_url: String,
    graphic_designers_url: String,
    social_media_managers_url: String,
    fitness_trainers_url: String,
    catering_services_url: String,
    ugc_content_creators_url: String,
    // ── Payments ─────────────────────────────────────────────────────────
    payments_url: String,
    // ── Employees (Internal) ─────────────────────────────────────────────
    employees_url: String,
    client: reqwest::Client,
}

impl Services {
    fn from_env() -> Self {
        Self {
            users_url: std::env::var("USERS_SERVICE_URL")
                .expect("USERS_SERVICE_URL must be set"),
            companies_url: std::env::var("COMPANIES_SERVICE_URL")
                .expect("COMPANIES_SERVICE_URL must be set"),
            jobs_url: std::env::var("JOBS_SERVICE_URL")
                .expect("JOBS_SERVICE_URL must be set"),
            job_seekers_url: std::env::var("JOB_SEEKERS_SERVICE_URL")
                .expect("JOB_SEEKERS_SERVICE_URL must be set"),
            customers_url: std::env::var("CUSTOMERS_SERVICE_URL")
                .expect("CUSTOMERS_SERVICE_URL must be set"),
            photographers_url: std::env::var("PHOTOGRAPHERS_SERVICE_URL")
                .expect("PHOTOGRAPHERS_SERVICE_URL must be set"),
            makeup_artists_url: std::env::var("MAKEUP_ARTISTS_SERVICE_URL")
                .expect("MAKEUP_ARTISTS_SERVICE_URL must be set"),
            tutors_url: std::env::var("TUTORS_SERVICE_URL")
                .expect("TUTORS_SERVICE_URL must be set"),
            developers_url: std::env::var("DEVELOPERS_SERVICE_URL")
                .expect("DEVELOPERS_SERVICE_URL must be set"),
            video_editors_url: std::env::var("VIDEO_EDITORS_SERVICE_URL")
                .expect("VIDEO_EDITORS_SERVICE_URL must be set"),
            graphic_designers_url: std::env::var("GRAPHIC_DESIGNERS_SERVICE_URL")
                .expect("GRAPHIC_DESIGNERS_SERVICE_URL must be set"),
            social_media_managers_url: std::env::var("SOCIAL_MEDIA_MANAGERS_SERVICE_URL")
                .expect("SOCIAL_MEDIA_MANAGERS_SERVICE_URL must be set"),
            fitness_trainers_url: std::env::var("FITNESS_TRAINERS_SERVICE_URL")
                .expect("FITNESS_TRAINERS_SERVICE_URL must be set"),
            catering_services_url: std::env::var("CATERING_SERVICES_SERVICE_URL")
                .expect("CATERING_SERVICES_SERVICE_URL must be set"),
            ugc_content_creators_url: std::env::var("UGC_CONTENT_CREATORS_SERVICE_URL")
                .expect("UGC_CONTENT_CREATORS_SERVICE_URL must be set"),
            payments_url: std::env::var("PAYMENTS_SERVICE_URL")
                .expect("PAYMENTS_SERVICE_URL must be set"),
            employees_url: std::env::var("EMPLOYEES_SERVICE_URL")
                .expect("EMPLOYEES_SERVICE_URL must be set"),
            client: reqwest::Client::new(),
        }
    }


    fn resolve_upstream(&self, path: &str) -> Option<String> {
        // Auth, users, roles, notifications, runtime-config, config, KB, support, reviews
        if path.starts_with("/api/auth")
            || path.starts_with("/api/users")
            || path.starts_with("/api/v1/users")
            || path.starts_with("/api/me")
            || path.starts_with("/api/profile")
            || path.starts_with("/api/onboarding")
            || path.starts_with("/api/roles")
            || path.starts_with("/api/notifications")
            || path.starts_with("/api/config")
            || path.starts_with("/api/kb")
            || path.starts_with("/api/packages")
            || path.starts_with("/api/support")
            || path.starts_with("/api/reviews")
            || path.starts_with("/api/admin/roles")
            || path.starts_with("/api/admin/users")
            || path.starts_with("/api/admin/verifications")
            || path.starts_with("/api/admin/approvals")
            || path.starts_with("/api/admin/approval-cases")
            || path.starts_with("/api/admin/external-roles")
            || path.starts_with("/api/admin/dashboard-config")
            || path.starts_with("/api/admin/onboarding-config")
            || path.starts_with("/api/admin/dashboard")
            || path.starts_with("/api/admin/kb")
            || path.starts_with("/api/admin/support-cases")
            || path.starts_with("/api/admin/reviews")
            || path.starts_with("/api/admin/coupons")
            || path.starts_with("/api/admin/discounts")
            || path.starts_with("/api/admin/tracecoin-packages")
            || path.starts_with("/api/admin/reports")
            || path.starts_with("/api/admin/activity-logs")
        {
            Some(self.users_url.clone())
        }
        // ── Employees / Internal Admin (NEW) ──────────────────────────────
        else if path.starts_with("/api/admin/auth")
            || path.starts_with("/api/admin/employees")
            || path.starts_with("/api/admin/departments")
            || path.starts_with("/api/admin/designations")
        {
            Some(self.employees_url.clone())
        }
        // Companies + Applications + Packages
        else if path.starts_with("/api/companies")
            || path.starts_with("/api/applications")
            || path.starts_with("/api/pricing")
            || path.starts_with("/api/admin/companies")
            || path.starts_with("/api/admin/applications")
        {
            Some(self.companies_url.clone())
        }
        // Job Seekers — must come BEFORE /api/jobs to avoid prefix collision
        else if path.starts_with("/api/jobseeker") {
            Some(self.job_seekers_url.clone())
        }
        // Jobs (separate service)
        else if path.starts_with("/api/jobs")
            || path.starts_with("/api/admin/jobs")
        {
            Some(self.jobs_url.clone())
        }
        // Customers + Leads
        else if path.starts_with("/api/customers")
            || path.starts_with("/api/admin/customers")
            || path.starts_with("/api/admin/leads")
        {
            Some(self.customers_url.clone())
        }
        // ── 9 Separate Profession Services ────────────────────────────────
        else if path.starts_with("/api/photographers") || path.starts_with("/api/admin/photographers") {
            Some(self.photographers_url.clone())
        }
        else if path.starts_with("/api/makeup-artists") || path.starts_with("/api/admin/makeup-artists") {
            Some(self.makeup_artists_url.clone())
        }
        else if path.starts_with("/api/tutors") || path.starts_with("/api/admin/tutors") {
            Some(self.tutors_url.clone())
        }
        else if path.starts_with("/api/developers") || path.starts_with("/api/admin/developers") {
            Some(self.developers_url.clone())
        }
        else if path.starts_with("/api/video-editors") || path.starts_with("/api/admin/video-editors") {
            Some(self.video_editors_url.clone())
        }
        else if path.starts_with("/api/graphic-designers") || path.starts_with("/api/admin/graphic-designers") {
            Some(self.graphic_designers_url.clone())
        }
        else if path.starts_with("/api/social-media-managers") || path.starts_with("/api/admin/social-media-managers") {
            Some(self.social_media_managers_url.clone())
        }
        else if path.starts_with("/api/fitness-trainers") || path.starts_with("/api/admin/fitness-trainers") {
            Some(self.fitness_trainers_url.clone())
        }
        else if path.starts_with("/api/catering-services") || path.starts_with("/api/admin/catering-services") {
            Some(self.catering_services_url.clone())
        }
        else if path.starts_with("/api/ugc-content-creators") {
            Some(self.ugc_content_creators_url.clone())
        }
        // ── Payments + Invoices ───────────────────────────────────────────
        else if path.starts_with("/api/payments")
            || path.starts_with("/api/admin/invoices")
            || path.starts_with("/api/admin/credits")
            || path.starts_with("/api/admin/revenue")
            || path.starts_with("/api/admin/pricing")
        {
            Some(self.payments_url.clone())
        }
        // Wallet / Tracecoins (shared across profession services — route to a credits service)
        else if path.starts_with("/api/credits") {
            Some(self.payments_url.clone())
        }
        // ── Ask Ash AI Credits — split across two services (see docs/ASK_ASH_BILLING_ARCHITECTURE.md).
        // Wallet balance + feature-charge live in the users service; package
        // catalog + PayU order/verify live in the payments service. Both
        // sub-paths start with "/api/ai", so they must be matched BEFORE the
        // generic AI-chat rule below, or everything here silently falls
        // through to the users service and 404s.
        else if path.starts_with("/api/ai-credits/wallet") || path.starts_with("/api/ai-credits/charge") {
            Some(self.users_url.clone())
        }
        else if path.starts_with("/api/ai-credits") {
            Some(self.payments_url.clone())
        }
        // ── AI Chat (routes to users service, which calls Ollama directly) ───
        else if path.starts_with("/api/ai") {
            Some(self.users_url.clone())
        }
        // Admin: AI credit package catalog lives alongside the public
        // catalog + PayU order/verify in the payments service. All other
        // /api/admin/ai-credits/* (balance/ledger/adjust/reconcile) falls
        // through to the generic /api/admin/ -> users rule below.
        else if path.starts_with("/api/admin/ai-credits/packages") {
            Some(self.payments_url.clone())
        }
        // Admin runtime config management defaults to users service
        else if path.starts_with("/api/admin/runtime-configs") {
            Some(self.users_url.clone())
        }
        // User-facing runtime config (role + permissions bundle)
        else if path.starts_with("/api/runtime-config") {
            Some(self.users_url.clone())
        }
        // Catch-all for any other admin endpoints → users service
        else if path.starts_with("/api/admin/") {
            Some(self.users_url.clone())
        }
        else {
            None
        }
    }
}

fn build_cors() -> CorsLayer {
    let frontend_url = std::env::var("FRONTEND_URL")
        .expect("FRONTEND_URL must be set");
    let admin_url = std::env::var("ADMIN_URL")
        .expect("ADMIN_URL must be set");

    let allowed_origins: Vec<HeaderValue> = vec![
        frontend_url.parse().expect("Invalid FRONTEND_URL"),
        admin_url.parse().expect("Invalid ADMIN_URL"),
    ];

    CorsLayer::new()
        .allow_origin(AllowOrigin::list(allowed_origins))
        .allow_methods([
            Method::GET,
            Method::POST,
            Method::PUT,
            Method::PATCH,
            Method::DELETE,
            Method::OPTIONS,
        ])
        .allow_headers(AllowHeaders::mirror_request())
        .allow_credentials(true)
}

#[tokio::main]
async fn main() {
    tracing_subscriber::registry()
        .with(tracing_subscriber::EnvFilter::new(
            std::env::var("RUST_LOG").unwrap_or_else(|_| "info".into()),
        ))
        .with(tracing_subscriber::fmt::layer())
        .init();

    let services = Services::from_env();

    let cors = build_cors();

    let app = Router::new()
        .route("/api/{*path}", any(proxy_handler))
        .route("/health", any(|| async { "Gateway OK" }))
        .layer(cors)
        .layer(SetResponseHeaderLayer::if_not_present(
            axum::http::header::X_FRAME_OPTIONS,
            HeaderValue::from_static("DENY"),
        ))
        .layer(SetResponseHeaderLayer::if_not_present(
            axum::http::header::X_CONTENT_TYPE_OPTIONS,
            HeaderValue::from_static("nosniff"),
        ))
        .layer(SetResponseHeaderLayer::if_not_present(
            axum::http::header::STRICT_TRANSPORT_SECURITY,
            HeaderValue::from_static("max-age=31536000; includeSubDomains"),
        ))
        .layer(SetResponseHeaderLayer::if_not_present(
            axum::http::header::REFERRER_POLICY,
            HeaderValue::from_static("strict-origin-when-cross-origin"),
        ))
        .layer(SetResponseHeaderLayer::if_not_present(
            axum::http::header::CONTENT_SECURITY_POLICY,
            HeaderValue::from_static("default-src 'self'"),
        ))
        .with_state(services);

    let port: u16 = std::env::var("PORT")
        .unwrap_or_else(|_| "9100".to_string())
        .parse()
        .expect("PORT must be a valid u16");
    let addr = SocketAddr::from(([0, 0, 0, 0], port));

    tracing::info!("Gateway listening on {} (routing v2)", addr);

    let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn proxy_handler(
    State(services): State<Services>,
    req: Request,
) -> impl IntoResponse {
    let path = req.uri().path().to_string();
    let query = req.uri().query().map(|q| format!("?{}", q)).unwrap_or_default();

    let Some(upstream_base) = services.resolve_upstream(&path) else {
        return (StatusCode::NOT_FOUND, "Route not found in gateway").into_response();
    };

    let target_url = format!("{}{}{}", upstream_base, path, query);
    let method = req.method().clone();
    let headers = req.headers().clone();
    let body = req.into_body();

    match services
        .client
        .request(method, &target_url)
        .headers(headers)
        .body(reqwest::Body::wrap_stream(body.into_data_stream()))
        .send()
        .await
    {
        Ok(res) => {
            let status = StatusCode::from_u16(res.status().as_u16()).unwrap_or(StatusCode::BAD_GATEWAY);
            let res_headers = res.headers().clone();
            let bytes = res.bytes().await.unwrap_or_default();
            let res_body = Body::from(bytes);

            let mut response = res_body.into_response();
            *response.status_mut() = status;
            for (name, value) in res_headers {
                if let Some(name) = name {
                    response.headers_mut().insert(name, value);
                }
            }
            response
        }
        Err(e) => {
            tracing::error!("Gateway proxy error → {}: {}", target_url, e);
            (StatusCode::BAD_GATEWAY, format!("Gateway error: {}", e)).into_response()
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn test_services() -> Services {
        Services {
            users_url: "http://users".to_string(),
            companies_url: "http://companies".to_string(),
            jobs_url: "http://jobs".to_string(),
            job_seekers_url: "http://job-seekers".to_string(),
            customers_url: "http://customers".to_string(),
            photographers_url: "http://photographers".to_string(),
            makeup_artists_url: "http://makeup-artists".to_string(),
            tutors_url: "http://tutors".to_string(),
            developers_url: "http://developers".to_string(),
            video_editors_url: "http://video-editors".to_string(),
            graphic_designers_url: "http://graphic-designers".to_string(),
            social_media_managers_url: "http://social-media-managers".to_string(),
            fitness_trainers_url: "http://fitness-trainers".to_string(),
            catering_services_url: "http://catering-services".to_string(),
            ugc_content_creators_url: "http://ugc-content-creators".to_string(),
            payments_url: "http://payments".to_string(),
            employees_url: "http://employees".to_string(),
            client: reqwest::Client::new(),
        }
    }

    /// Regression test: /api/ai-credits/* must split correctly across
    /// users (wallet/charge) and payments (packages/order/verify), and
    /// must NOT fall through to the generic /api/ai -> users rule, which
    /// it would silently do if the ai-credits-specific checks were ever
    /// removed or reordered after the generic AI-chat rule.
    #[test]
    fn ai_credits_routes_split_correctly_between_users_and_payments() {
        let services = test_services();

        assert_eq!(services.resolve_upstream("/api/ai-credits/wallet"), Some("http://users".to_string()));
        assert_eq!(services.resolve_upstream("/api/ai-credits/charge"), Some("http://users".to_string()));

        assert_eq!(services.resolve_upstream("/api/ai-credits"), Some("http://payments".to_string()));
        assert_eq!(services.resolve_upstream("/api/ai-credits/order"), Some("http://payments".to_string()));
        assert_eq!(services.resolve_upstream("/api/ai-credits/verify"), Some("http://payments".to_string()));

        // Plain /api/ai/* (chat, generate-job-field, etc.) is unaffected.
        assert_eq!(services.resolve_upstream("/api/ai/generate-job-field"), Some("http://users".to_string()));
    }

    #[test]
    fn admin_ai_credits_routes_split_correctly() {
        let services = test_services();

        assert_eq!(services.resolve_upstream("/api/admin/ai-credits/packages"), Some("http://payments".to_string()));
        assert_eq!(services.resolve_upstream("/api/admin/ai-credits/packages/abc-123"), Some("http://payments".to_string()));

        // Everything else under /api/admin/ai-credits/* falls through to
        // the generic /api/admin/ -> users rule.
        assert_eq!(services.resolve_upstream("/api/admin/ai-credits/balance"), Some("http://users".to_string()));
        assert_eq!(services.resolve_upstream("/api/admin/ai-credits/ledger"), Some("http://users".to_string()));
        assert_eq!(services.resolve_upstream("/api/admin/ai-credits/adjust"), Some("http://users".to_string()));
        assert_eq!(services.resolve_upstream("/api/admin/ai-credits/reconcile"), Some("http://users".to_string()));
    }
}
