pub mod agent;
mod agents_admin;
pub mod ai_admin;
mod api_keys_admin;
mod users_admin;
pub mod crypto;
pub mod ai_budget;
pub mod ai_edges;
pub mod ai_process;
pub mod audio;
pub mod bandwidth;
mod auth;
pub mod cas;
pub mod cli_dispatch;
pub mod clip;
pub mod highlight;
mod custom_domain;
mod embed_player;
pub mod describe_image;
mod intentions;
pub mod jobs;
pub mod livekit;
pub mod maintenance;
pub mod metrics;
pub mod pruning;
mod queries;
pub mod pg_writes;
pub mod publishing;
pub mod health;
pub mod resource_usage;
pub mod resources;
mod rss;
mod serving;
pub mod summarize;
pub mod ws;
pub mod mixer;
pub mod feed_poller;
pub mod calendar_poller;
pub mod orchestration_trigger;
mod webhook;
mod webhook_admin;
mod webhook_templates;
pub mod script_compiler;
pub mod script_executor;
pub mod tiptap;
pub mod transcribe;
pub mod tts;
pub mod podcast_import;
pub mod podcast_stats;
pub mod usage_overview;
pub mod user_usage;
mod workspace;

use axum::{extract::State, http::StatusCode, middleware, routing::{get, post}, Json, Router};
use serde::{Deserialize, Serialize};
use sqlx::postgres::PgPoolOptions;
use sqlx::PgPool;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};

use auth::{AuthUser, JwksKeys};
use cas::CasStore;
use ws::WsBroadcast;

#[derive(Clone)]
pub struct AppState {
    pub db: PgPool,
    pub jwks: JwksKeys,
    pub cas: CasStore,
    pub index_cache: publishing::IndexCache,
    pub dynamic_page_cache: publishing::DynamicPageCache,
    pub maintenance: maintenance::MaintenanceState,
    pub priority_rules: resources::PriorityRules,
    pub metrics: metrics::MetricsCollector,
    pub ws_broadcast: WsBroadcast,
}

#[derive(Serialize)]
struct HealthResponse {
    status: &'static str,
    version: &'static str,
    db: &'static str,
}

#[derive(Serialize)]
struct MeResponse {
    node_id: uuid::Uuid,
    authentik_sub: String,
}

#[derive(Deserialize)]
struct AuthSyncRequest {
    username: String,
}

#[derive(Serialize)]
struct AuthSyncResponse {
    ok: bool,
}

#[tokio::main]
async fn main() {
    // Strukturert logging: LOG_FORMAT=json for maskinlesbart, ellers human-readable.
    let env_filter = EnvFilter::try_from_default_env()
        .unwrap_or_else(|_| "maskinrommet=info,tower_http=info".parse().unwrap());
    let log_format = std::env::var("LOG_FORMAT").unwrap_or_default();
    if log_format == "json" {
        tracing_subscriber::registry()
            .with(env_filter)
            .with(tracing_subscriber::fmt::layer().json())
            .init();
    } else {
        tracing_subscriber::registry()
            .with(env_filter)
            .with(tracing_subscriber::fmt::layer())
            .init();
    }

    // Database
    let database_url = std::env::var("DATABASE_URL")
        .unwrap_or_else(|_| "postgres://sidelinja:sidelinja@localhost:5432/synops".to_string());

    let db = PgPoolOptions::new()
        .max_connections(10)
        .connect(&database_url)
        .await
        .expect("Kunne ikke koble til PostgreSQL");

    tracing::info!("Koblet til PostgreSQL");

    // JWKS — hent nøkler fra Authentik ved oppstart
    let issuer = std::env::var("AUTHENTIK_ISSUER")
        .unwrap_or_else(|_| "https://auth.synops.no/application/o/sidelinja/".to_string());

    let client_id = std::env::var("AUTHENTIK_CLIENT_ID")
        .expect("AUTHENTIK_CLIENT_ID må være satt");

    let jwks = JwksKeys::fetch(&issuer, &client_id)
        .await
        .expect("Kunne ikke hente JWKS fra Authentik");

    // CAS — content-addressable store for binærfiler
    let cas_root = std::env::var("CAS_ROOT")
        .unwrap_or_else(|_| "/srv/synops/media/cas".to_string());
    let cas = CasStore::new(&cas_root)
        .await
        .expect("Kunne ikke opprette CAS-katalog");
    tracing::info!(root = %cas_root, "CAS initialisert");

    // Vedlikeholdstilstand (oppgave 15.2)
    let maintenance = maintenance::MaintenanceState::new();

    // Last prioritetsregler fra PG (oppgave 15.5)
    let priority_rules = resources::PriorityRules::load(&db)
        .await
        .expect("Kunne ikke laste prioritetsregler fra PG");

    let index_cache = publishing::new_index_cache();

    // Start jobbkø-worker i bakgrunnen (med ressursstyring, oppgave 15.5)
    jobs::start_worker(db.clone(), cas.clone(), index_cache.clone(), maintenance.clone(), priority_rules.clone());

    // Start periodisk CAS-pruning i bakgrunnen
    pruning::start_pruning_loop(db.clone(), cas.clone());

    // Start disk-overvåking i bakgrunnen (oppgave 15.5)
    resources::start_disk_monitor(db.clone());

    // Start planlagt publisering-scheduler i bakgrunnen
    publishing::start_publish_scheduler(db.clone());

    // Start A/B-evaluator i bakgrunnen (oppgave 14.17)
    publishing::start_ab_evaluator(db.clone());

    // Start nattlig bandwidth-parsing (oppgave 15.7)
    bandwidth::start_bandwidth_parser(db.clone());

    // Start periodisk CAS tmp-opprydding (oppgave 17.6)
    cas::start_tmp_cleanup_loop(cas.clone());

    // Start feed-poller for RSS/Atom-abonnementer (oppgave 29.3)
    feed_poller::start_feed_poller(db.clone());

    // Start calendar-poller for CalDAV/ICS-abonnementer (oppgave 29.12)
    calendar_poller::start_calendar_poller(db.clone());
    let dynamic_page_cache = publishing::new_dynamic_page_cache();
    let metrics = metrics::MetricsCollector::new();

    // WebSocket broadcast-kanal og PG LISTEN/NOTIFY-lytter (oppgave 22.1)
    let ws_broadcast = WsBroadcast::new();
    ws::start_pg_listener(db.clone(), ws_broadcast.clone());

    let state = AppState { db, jwks, cas, index_cache, dynamic_page_cache, maintenance, priority_rules, metrics, ws_broadcast };

    // Ruter: /health er offentlig, /me krever gyldig JWT
    let app = Router::new()
        .route("/health", get(health))
        .route("/me", get(me))
        .route("/auth/sync", post(auth_sync))
        // WebSocket-endepunkt for sanntid via PG LISTEN/NOTIFY (oppgave 22.1)
        .route("/ws", get(ws::ws_handler))
        .route("/intentions/create_node", post(intentions::create_node))
        .route("/intentions/create_edge", post(intentions::create_edge))
        .route("/intentions/update_node", post(intentions::update_node))
        .route("/intentions/delete_node", post(intentions::delete_node))
        .route("/intentions/update_edge", post(intentions::update_edge))
        .route("/intentions/delete_edge", post(intentions::delete_edge))
        .route("/intentions/set_slot", post(intentions::set_slot))
        .route("/intentions/create_communication", post(intentions::create_communication))
        .route("/intentions/upload_media", post(intentions::upload_media))
        .route("/cas/{hash}", get(serving::get_cas_file))
        .route("/query/nodes", get(queries::query_nodes))
        .route("/query/board", get(queries::query_board))
        .route("/query/editorial_board", get(queries::query_editorial_board))
        .route("/query/segments", get(queries::query_segments))
        .route("/query/segments/srt", get(queries::export_srt))
        .route("/intentions/create_alias", post(intentions::create_alias))
        .route("/intentions/update_segment", post(intentions::update_segment))
        .route("/intentions/retranscribe", post(intentions::retranscribe))
        .route("/intentions/resolve_retranscription", post(intentions::resolve_retranscription))
        .route("/intentions/summarize", post(intentions::summarize))
        .route("/intentions/ai_process", post(intentions::ai_process))
        .route("/intentions/create_ai_preset", post(intentions::create_ai_preset))
        .route("/intentions/generate_tts", post(intentions::generate_tts))
        .route("/intentions/join_communication", post(intentions::join_communication))
        .route("/intentions/leave_communication", post(intentions::leave_communication))
        .route("/intentions/close_communication", post(intentions::close_communication))
        .route("/query/aliases", get(queries::query_aliases))
        .route("/query/graph", get(queries::query_graph))
        .route("/query/presentation_elements", get(queries::query_presentation_elements))
        .route("/query/transcription_versions", get(queries::query_transcription_versions))
        .route("/query/segments_version", get(queries::query_segments_version))
        .route("/intentions/audio_analyze", post(intentions::audio_analyze))
        .route("/intentions/audio_process", post(intentions::audio_process))
        .route("/intentions/ab_override", post(publishing::ab_override))
        // Systemvarsler (oppgave 15.1)
        .route("/intentions/create_announcement", post(intentions::create_announcement))
        .route("/intentions/expire_announcement", post(intentions::expire_announcement))
        // Vedlikeholdsmodus (oppgave 15.2)
        .route("/intentions/initiate_maintenance", post(intentions::initiate_maintenance))
        .route("/intentions/cancel_maintenance", post(intentions::cancel_maintenance))
        .route("/admin/maintenance_status", get(intentions::maintenance_status))
        // Jobbkø-oversikt (oppgave 15.3)
        .route("/admin/jobs", get(intentions::list_jobs))
        .route("/intentions/retry_job", post(intentions::retry_job))
        .route("/intentions/cancel_job", post(intentions::cancel_job))
        // Ressursstyring (oppgave 15.5)
        .route("/admin/resources", get(intentions::resource_status))
        .route("/admin/resources/disk", get(intentions::resource_disk))
        .route("/admin/resources/update_rule", post(intentions::update_priority_rule))
        // AI Gateway-konfigurasjon (oppgave 15.4)
        .route("/admin/ai", get(ai_admin::ai_overview))
        .route("/admin/ai/usage", get(ai_admin::ai_usage))
        .route("/admin/ai/update_alias", post(ai_admin::update_alias))
        .route("/admin/ai/create_alias", post(ai_admin::create_alias))
        .route("/admin/ai/update_provider", post(ai_admin::update_provider))
        .route("/admin/ai/create_provider", post(ai_admin::create_provider))
        .route("/admin/ai/delete_provider", post(ai_admin::delete_provider))
        .route("/admin/ai/update_routing", post(ai_admin::update_routing))
        .route("/admin/ai/delete_routing", post(ai_admin::delete_routing))
        .route("/admin/ai/test_prompt", post(ai_admin::test_prompt))
        .route("/admin/ai/tier_costs", get(ai_admin::tier_costs))
        // Forbruksoversikt (oppgave 15.8)
        .route("/admin/usage", get(usage_overview::usage_overview))
        // Podcast-statistikk (oppgave 30.4)
        .route("/admin/podcast/stats", get(podcast_stats::podcast_stats))
        // Podcast-import wizard (oppgave 30.7)
        .route("/admin/podcast/import-preview", post(podcast_import::import_preview))
        .route("/admin/podcast/import", post(podcast_import::import_start))
        .route("/admin/podcast/import-status", get(podcast_import::import_status))
        .route("/admin/podcast/collections", get(podcast_import::podcast_collections))
        // Brukersynlig forbruk (oppgave 15.9)
        .route("/my/usage", get(user_usage::my_usage))
        .route("/my/workspace", get(workspace::my_workspace))
        .route("/query/node_usage", get(user_usage::node_usage))
        // Serverhelse-dashboard (oppgave 15.6)
        .route("/admin/health", get(health::health_dashboard))
        .route("/admin/health/logs", get(health::health_logs))
        .route("/query/audio_info", get(intentions::audio_info))
        .route("/query/job_status", get(queries::query_job_status))
        .route("/pub/{slug}/feed.xml", get(rss::generate_feed))
        .route("/pub/{slug}", get(publishing::serve_index))
        // A/B-testing: klikk-sporing (oppgave 14.17)
        .route("/pub/{slug}/t/{article_id}", get(publishing::track_click))
        // Dynamiske sider: kategori, arkiv, søk, om (oppgave 14.15)
        .route("/pub/{slug}/kategori/{tag}", get(publishing::serve_category))
        .route("/pub/{slug}/arkiv", get(publishing::serve_archive))
        .route("/pub/{slug}/sok", get(publishing::serve_search))
        .route("/pub/{slug}/om", get(publishing::serve_about))
        .route("/pub/{slug}/preview/{theme}", get(publishing::preview_theme))
        // Embed podcast-spiller (oppgave 30.5)
        .route("/pub/{slug}/{episode_id}/player", get(embed_player::serve_player))
        // NB: {article_id} catch-all må komme etter de spesifikke rutene
        .route("/pub/{slug}/{article_id}", get(publishing::serve_article))
        // Custom domains: Caddy on-demand TLS callback
        .route("/internal/verify-domain", get(custom_domain::verify_domain))
        // Custom domains: domene-basert serving (Caddy proxyer hit)
        .route("/custom-domain/index", get(custom_domain::serve_custom_domain_index))
        .route("/custom-domain/feed.xml", get(custom_domain::serve_custom_domain_feed))
        // Dynamiske sider for custom domains
        .route("/custom-domain/kategori/{tag}", get(custom_domain::serve_custom_domain_category))
        .route("/custom-domain/arkiv", get(custom_domain::serve_custom_domain_archive))
        .route("/custom-domain/sok", get(custom_domain::serve_custom_domain_search))
        .route("/custom-domain/om", get(custom_domain::serve_custom_domain_about))
        .route("/custom-domain/{article_id}", get(custom_domain::serve_custom_domain_article))
        // Orkestrering UI (oppgave 24.6) + AI-assistert (oppgave 24.7)
        .route("/intentions/clip_url", post(intentions::clip_url))
        // Feed-abonnement (oppgave 29.3)
        .route("/intentions/configure_feed_subscription", post(intentions::configure_feed_subscription))
        .route("/intentions/remove_feed_subscription", post(intentions::remove_feed_subscription))
        // Kalender-abonnement (oppgave 29.12)
        .route("/intentions/configure_calendar_subscription", post(intentions::configure_calendar_subscription))
        .route("/intentions/remove_calendar_subscription", post(intentions::remove_calendar_subscription))
        .route("/intentions/compile_script", post(intentions::compile_script))
        .route("/intentions/test_orchestration", post(intentions::test_orchestration))
        .route("/intentions/ai_suggest_script", post(intentions::ai_suggest_script))
        .route("/query/orchestration_log", get(intentions::orchestration_log))
        // Utgående varsler (oppgave 26.7)
        .route("/intentions/send_notification", post(intentions::send_notification))
        // Mixer-kanaler
        .route("/intentions/create_mixer_channel", post(mixer::create_mixer_channel))
        .route("/intentions/set_gain", post(mixer::set_gain))
        .route("/intentions/set_mute", post(mixer::set_mute))
        .route("/intentions/toggle_effect", post(mixer::toggle_effect))
        .route("/intentions/set_mixer_role", post(mixer::set_mixer_role))
        // Agent-oversikt (oppgave 063)
        .route("/admin/agents", get(agents_admin::agents_overview))
        .route("/admin/agents/toggle", post(agents_admin::toggle_agent))
        // Brukeradministrasjon (oppgave 064)
        .route("/admin/users", get(users_admin::users_overview))
        .route("/admin/users/toggle", post(users_admin::toggle_user))
        .route("/admin/users/budget", post(users_admin::update_budget))
        // API-nøkler (oppgave 060)
        .route("/admin/api-keys", get(api_keys_admin::list_keys))
        .route("/admin/api-keys/create", post(api_keys_admin::create_key))
        .route("/admin/api-keys/test", post(api_keys_admin::test_key))
        .route("/admin/api-keys/deactivate", post(api_keys_admin::deactivate_key))
        .route("/admin/api-keys/delete", post(api_keys_admin::delete_key))
        // Webhook-admin (oppgave 29.5)
        .route("/admin/webhooks", get(webhook_admin::list_webhooks))
        .route("/admin/webhooks/events", get(webhook_admin::webhook_events))
        .route("/admin/webhooks/create", post(webhook_admin::create_webhook))
        .route("/admin/webhooks/regenerate_token", post(webhook_admin::regenerate_token))
        .route("/admin/webhooks/delete", post(webhook_admin::delete_webhook))
        .route("/admin/webhooks/templates", get(webhook_admin::list_templates))
        .route("/admin/webhooks/set_template", post(webhook_admin::set_template))
        // Webhook universell input (oppgave 29.4)
        .route("/api/webhook/{token}", post(webhook::handle_webhook))
        // Observerbarhet (oppgave 12.1)
        .route("/metrics", get(metrics::metrics_endpoint))
        .layer(middleware::from_fn_with_state(state.clone(), metrics::latency_middleware))
        .with_state(state);

    let bind = std::env::var("BIND_ADDR").unwrap_or_else(|_| "0.0.0.0:3100".to_string());
    let listener = tokio::net::TcpListener::bind(&bind).await.unwrap();
    tracing::info!("Maskinrommet lytter på {bind}");
    axum::serve(listener, app).await.unwrap();
}

/// Offentlig helsesjekk — verifiserer PG-tilkobling.
async fn health(State(state): State<AppState>) -> Result<Json<HealthResponse>, StatusCode> {
    sqlx::query("SELECT 1")
        .execute(&state.db)
        .await
        .map_err(|_| StatusCode::SERVICE_UNAVAILABLE)?;

    Ok(Json(HealthResponse {
        status: "ok",
        version: env!("CARGO_PKG_VERSION"),
        db: "connected",
    }))
}

/// Beskyttet endepunkt — returnerer autentisert brukers node_id.
/// Brukes for å verifisere at auth-middleware fungerer.
async fn me(user: AuthUser) -> Json<MeResponse> {
    Json(MeResponse {
        node_id: user.node_id,
        authentik_sub: user.authentik_sub,
    })
}

/// POST /auth/sync — synkroniser brukerprofil fra Authentik ved login.
/// Kalles av SvelteKit auth-callback etter vellykket innlogging.
/// Oppdaterer username i auth_identities fra Authentik preferred_username.
async fn auth_sync(
    user: AuthUser,
    State(state): State<AppState>,
    Json(body): Json<AuthSyncRequest>,
) -> Result<Json<AuthSyncResponse>, StatusCode> {
    let username = body.username.trim().to_lowercase();

    if username.is_empty() {
        tracing::warn!("auth_sync: tomt brukernavn for node_id={}", user.node_id);
        return Err(StatusCode::BAD_REQUEST);
    }

    sqlx::query(
        "UPDATE auth_identities SET username = $1 WHERE node_id = $2",
    )
    .bind(&username)
    .bind(user.node_id)
    .execute(&state.db)
    .await
    .map_err(|e| {
        tracing::error!("auth_sync: kunne ikke oppdatere username: {e}");
        StatusCode::INTERNAL_SERVER_ERROR
    })?;

    tracing::info!(
        "auth_sync: username='{}' for node_id={}",
        username,
        user.node_id
    );

    Ok(Json(AuthSyncResponse { ok: true }))
}
