mod api;
mod crypto_at_rest;
mod models;

use std::{env, net::SocketAddr};

use anyhow::Context;
use axum::{
    extract::{
        ws::{Message, WebSocket, WebSocketUpgrade},
        State,
    },
    response::IntoResponse,
    routing::{get, patch, post},
    Json, Router,
};
use chrono::Utc;
use companytool_shared_protocol::{
    crypto::SessionKey, ClientMessage, HelloAckMessage, ProtocolErrorMessage, RecordSummary,
    ServerMessage, WireMessage, PROTOCOL_VERSION,
};
use crypto_at_rest::DataCrypto;
use futures_util::{SinkExt, StreamExt};
use sqlx::{PgPool, Row};
use tokio::sync::broadcast;
use tower_http::{cors::CorsLayer, trace::TraceLayer};
use tracing::{info, warn};
use uuid::Uuid;

#[derive(Clone)]
struct AppState {
    db: Option<PgPool>,
    crypto: DataCrypto,
    events: broadcast::Sender<ServerMessage>,
}

impl AppState {
    fn db(&self) -> Result<&PgPool, api::ApiError> {
        self.db
            .as_ref()
            .ok_or_else(|| api::ApiError::bad_request("Datenbank ist im Testmodus nicht aktiv"))
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    dotenvy::dotenv().ok();
    tracing_subscriber::fmt()
        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
        .init();

    let communication_test_mode = env::var("COMMUNICATION_TEST_MODE").as_deref() == Ok("1");
    let bind: SocketAddr = env::var("BACKEND_BIND")
        .unwrap_or_else(|_| "127.0.0.1:8080".to_string())
        .parse()
        .context("BACKEND_BIND ist keine gültige Adresse")?;

    let db = if communication_test_mode {
        warn!("COMMUNICATION_TEST_MODE aktiv: backend startet ohne datenbank");
        None
    } else {
        let database_url =
            env::var("DATABASE_URL").context("DATABASE_URL fehlt, siehe .env.example")?;
        let db = PgPool::connect(&database_url).await?;
        migrate(&db).await?;
        Some(db)
    };

    let (events, _) = broadcast::channel(256);
    let state = AppState {
        db,
        crypto: DataCrypto::from_env(),
        events,
    };
    let app = Router::new()
        .route("/health", get(health))
        .route("/ws", get(ws_handler))
        .route(
            "/api/v1/dev/bootstrap-local",
            post(api::dev_bootstrap_local),
        )
        .route(
            "/api/v1/registration/organization",
            post(api::register_organization),
        )
        .route(
            "/api/v1/admin/organization-registrations",
            get(api::list_organization_registrations),
        )
        .route(
            "/api/v1/admin/organization-registrations/:id",
            get(api::get_organization_registration),
        )
        .route(
            "/api/v1/admin/organization-registrations/:id/approve",
            post(api::approve_organization_registration),
        )
        .route(
            "/api/v1/admin/organization-registrations/:id/reject",
            post(api::reject_organization_registration),
        )
        .route(
            "/api/v1/admin/organization-registrations/:id/resend-initial-email",
            post(api::resend_initial_email),
        )
        .route(
            "/api/v1/admin/organization-registrations/:id/retry-provisioning",
            post(api::retry_provisioning),
        )
        .route("/api/v1/auth/login", post(api::login))
        .route(
            "/api/v1/auth/change-initial-password",
            post(api::change_initial_password),
        )
        .route(
            "/api/v1/auth/request-password-reset",
            post(api::request_password_reset),
        )
        .route("/api/v1/auth/reset-password", post(api::reset_password))
        .route(
            "/api/v1/auth/accept-invitation",
            post(api::accept_invitation),
        )
        .route("/api/v1/auth/organizations", get(api::auth_organizations))
        .route(
            "/api/v1/auth/select-organization",
            post(api::select_organization),
        )
        .route(
            "/api/v1/organizations/current/setup",
            get(api::get_organization_setup).put(api::put_organization_setup),
        )
        .route(
            "/api/v1/users/me/settings/navigation",
            get(api::get_user_navigation_settings).put(api::put_user_navigation_settings),
        )
        .route("/api/v1/number-ranges", get(api::list_number_ranges))
        .route(
            "/api/v1/number-ranges/:code/next",
            axum::routing::post(api::generate_next_number),
        )
        .route(
            "/api/v1/number-ranges/:code",
            axum::routing::put(api::update_number_range),
        )
        .route(
            "/api/v1/customers",
            get(api::list_customers).post(api::create_customer),
        )
        .route(
            "/api/v1/customers/:customer_id",
            axum::routing::put(api::update_customer).delete(api::delete_customer),
        )
        .route(
            "/api/v1/suppliers",
            get(api::list_suppliers).post(api::create_supplier),
        )
        .route(
            "/api/v1/suppliers/:supplier_id",
            axum::routing::put(api::update_supplier).delete(api::delete_supplier),
        )
        .route("/api/v1/items", get(api::list_items).post(api::create_item))
        .route(
            "/api/v1/items/:item_id/prices",
            get(api::list_item_price_history),
        )
        .route(
            "/api/v1/items/:item_id",
            axum::routing::put(api::update_item).delete(api::delete_item),
        )
        .route(
            "/api/v1/cash-discount-terms",
            get(api::list_cash_discount_terms).post(api::create_cash_discount_term),
        )
        .route(
            "/api/v1/cash-discount-terms/:term_id",
            axum::routing::put(api::update_cash_discount_term)
                .delete(api::delete_cash_discount_term),
        )
        .route(
            "/api/v1/quotes",
            get(api::list_quotes).post(api::create_quote),
        )
        .route(
            "/api/v1/quotes/:quote_id",
            axum::routing::put(api::update_quote).delete(api::delete_quote),
        )
        .route(
            "/api/v1/quotes/:quote_id/convert-to-invoice",
            post(api::convert_quote_to_outgoing_invoice),
        )
        .route(
            "/api/v1/outgoing-invoices",
            get(api::list_outgoing_invoices).post(api::create_outgoing_invoice),
        )
        .route(
            "/api/v1/outgoing-invoices/:invoice_id",
            axum::routing::put(api::update_outgoing_invoice).delete(api::delete_outgoing_invoice),
        )
        .route(
            "/api/v1/outgoing-invoices/:invoice_id/finalize",
            post(api::finalize_outgoing_invoice),
        )
        .route(
            "/api/v1/incoming-invoices",
            get(api::list_incoming_invoices).post(api::create_incoming_invoice),
        )
        .route(
            "/api/v1/incoming-invoices/:invoice_id",
            axum::routing::put(api::update_incoming_invoice).delete(api::delete_incoming_invoice),
        )
        .route(
            "/api/v1/imports/price-list/preview",
            post(api::preview_price_list_import),
        )
        .route(
            "/api/v1/imports/price-list/apply",
            post(api::apply_price_list_import),
        )
        .route(
            "/api/v1/api-connectors",
            get(api::list_api_connectors).post(api::create_api_connector),
        )
        .route(
            "/api/v1/api-connectors/:connector_id",
            axum::routing::put(api::update_api_connector).delete(api::delete_api_connector),
        )
        .route(
            "/api/v1/api-connectors/:connector_id/sync",
            post(api::sync_api_connector),
        )
        .route(
            "/api/v1/price-rules",
            get(api::list_price_rules).post(api::create_price_rule),
        )
        .route(
            "/api/v1/price-rules/:rule_id",
            axum::routing::put(api::update_price_rule).delete(api::delete_price_rule),
        )
        .route(
            "/api/v1/activities",
            get(api::list_activities).post(api::create_activity),
        )
        .route(
            "/api/v1/activities/:activity_id",
            axum::routing::put(api::update_activity).delete(api::delete_activity),
        )
        .route(
            "/api/v1/communications",
            get(api::list_communications).post(api::create_communication),
        )
        .route(
            "/api/v1/communications/:communication_id",
            axum::routing::put(api::update_communication).delete(api::delete_communication),
        )
        .route(
            "/api/v1/documents",
            get(api::list_documents).post(api::upload_document),
        )
        .route(
            "/api/v1/documents/:document_id/download",
            get(api::download_document),
        )
        .route(
            "/api/v1/documents/:document_id/audit-log",
            get(api::list_document_audit_log),
        )
        .route(
            "/api/v1/documents/:document_id",
            axum::routing::delete(api::delete_document),
        )
        .route(
            "/api/v1/organizations/current/invitations",
            post(api::invite_user),
        )
        .route("/api/v1/organizations/current/users", get(api::list_users))
        .route(
            "/api/v1/organizations/current/users/:user_id/roles",
            patch(api::update_user_roles),
        )
        .route(
            "/api/v1/organizations/current/users/:user_id/disable",
            post(api::disable_user),
        )
        .with_state(state)
        .layer(CorsLayer::permissive())
        .layer(TraceLayer::new_for_http());

    let listener = tokio::net::TcpListener::bind(bind).await?;
    info!("backend lauscht auf http://{bind}");
    axum::serve(listener, app)
        .with_graceful_shutdown(shutdown_signal())
        .await?;

    Ok(())
}

async fn health() -> Json<serde_json::Value> {
    Json(serde_json::json!({ "status": "ok" }))
}

async fn ws_handler(ws: WebSocketUpgrade, State(state): State<AppState>) -> impl IntoResponse {
    ws.on_upgrade(move |socket| handle_socket(socket, state))
}

async fn handle_socket(socket: WebSocket, state: AppState) {
    let (mut sender, mut receiver) = socket.split();
    let mut events = state.events.subscribe();
    let mut session_key: Option<SessionKey> = None;

    loop {
        tokio::select! {
            Some(Ok(message)) = receiver.next() => {
                if let Message::Text(text) = message {
                    if let Some(new_session_key) = handle_client_wire_message(&text, &state, &mut sender, session_key.as_ref()).await {
                        if let Ok(snapshot) = load_snapshot(state.db.as_ref()).await {
                            if send_encrypted_server_message(
                                &mut sender,
                                &new_session_key,
                                ServerMessage::Snapshot { records: snapshot },
                            ).await.is_err() {
                                break;
                            }
                        }
                        session_key = Some(new_session_key);
                    }
                }
            }
            Ok(event) = events.recv() => {
                if let Some(active_session_key) = session_key.as_ref() {
                    if let Err(error) = send_encrypted_server_message(&mut sender, active_session_key, event).await {
                        warn!(%error, "server message konnte nicht gesendet werden");
                        break;
                    }
                }
            }
            else => break,
        }
    }
}

async fn handle_client_wire_message(
    text: &str,
    state: &AppState,
    sender: &mut futures_util::stream::SplitSink<WebSocket, Message>,
    session_key: Option<&SessionKey>,
) -> Option<SessionKey> {
    match serde_json::from_str::<WireMessage>(text) {
        Ok(WireMessage::Hello(hello)) => {
            if hello.protocol_version != PROTOCOL_VERSION {
                let _ = send_wire_error(sender, "nicht unterstützte Protokollversion").await;
                return None;
            }

            match SessionKey::from_base64(hello.key_id.clone(), &hello.session_key) {
                Ok(new_session_key) => {
                    let ack = WireMessage::HelloAck(HelloAckMessage {
                        protocol_version: PROTOCOL_VERSION,
                        key_id: hello.key_id,
                    });
                    if send_wire_message(sender, ack).await.is_err() {
                        return None;
                    }
                    Some(new_session_key)
                }
                Err(error) => {
                    let _ =
                        send_wire_error(sender, &format!("ungültiger Session-Key: {error}")).await;
                    None
                }
            }
        }
        Ok(WireMessage::Encrypted(envelope)) => {
            let Some(active_session_key) = session_key else {
                let _ = send_wire_error(sender, "verschlüsselte Nachricht vor hello").await;
                return None;
            };

            match active_session_key.decrypt::<ClientMessage>(&envelope) {
                Ok(message) => handle_client_message(message, state).await,
                Err(error) => {
                    let _ = send_wire_error(
                        sender,
                        &format!("Nachricht konnte nicht entschlüsselt werden: {error}"),
                    )
                    .await;
                }
            }
            None
        }
        Ok(WireMessage::HelloAck(_) | WireMessage::Error(_)) => None,
        Err(error) => {
            let _ = send_wire_error(sender, &format!("ungültige Wire-Nachricht: {error}")).await;
            None
        }
    }
}

async fn handle_client_message(message: ClientMessage, state: &AppState) {
    match message {
        ClientMessage::Ping => {
            let _ = state.events.send(ServerMessage::Pong);
        }
        ClientMessage::Subscribe { topic } => {
            info!(%topic, "client hat topic abonniert");
        }
    }
}

async fn send_encrypted_server_message(
    sender: &mut futures_util::stream::SplitSink<WebSocket, Message>,
    session_key: &SessionKey,
    message: ServerMessage,
) -> anyhow::Result<()> {
    let envelope = session_key.encrypt(&message)?;
    send_wire_message(sender, WireMessage::Encrypted(envelope)).await
}

async fn send_wire_error(
    sender: &mut futures_util::stream::SplitSink<WebSocket, Message>,
    message: &str,
) -> anyhow::Result<()> {
    send_wire_message(
        sender,
        WireMessage::Error(ProtocolErrorMessage {
            message: message.to_string(),
        }),
    )
    .await
}

async fn send_wire_message(
    sender: &mut futures_util::stream::SplitSink<WebSocket, Message>,
    message: WireMessage,
) -> anyhow::Result<()> {
    let text = serde_json::to_string(&message)?;
    sender.send(Message::Text(text)).await?;
    Ok(())
}

async fn migrate(db: &PgPool) -> anyhow::Result<()> {
    sqlx::migrate!("./migrations").run(db).await?;
    api::sync_all_company_schemas(db).await?;
    Ok(())
}

async fn load_snapshot(db: Option<&PgPool>) -> anyhow::Result<Vec<RecordSummary>> {
    let Some(db) = db else {
        return Ok(vec![RecordSummary {
            id: Uuid::nil(),
            title: "Kommunikationstest-Datensatz".to_string(),
            updated_at: Utc::now(),
        }]);
    };

    let rows = sqlx::query("select id, title, updated_at from records order by updated_at desc")
        .fetch_all(db)
        .await?;

    Ok(rows
        .into_iter()
        .map(|row| RecordSummary {
            id: row.get("id"),
            title: row.get("title"),
            updated_at: row.get("updated_at"),
        })
        .collect())
}

async fn shutdown_signal() {
    let _ = tokio::signal::ctrl_c().await;
}
