/*
    This file is part of jellything (https://codeberg.org/metamuffin/jellything)
    which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
    Copyright (C) 2026 metamuffin <metamuffin.org>
*/
#![feature(int_roundings, str_as_str, duration_constructors)]
#![allow(clippy::needless_borrows_for_generic_args)]
#![recursion_limit = "4096"]

use crate::{
    auth::{hash_password, token::SessionKey},
    logger::setup_logger,
};
use anyhow::{Result, anyhow};
use jellycache::Cache;
use jellycommon::{
    USER_ADMIN, USER_LOGIN, USER_PASSWORD,
    jellyobject::{OBB, Path},
};
use jellydb::{Database, Filter, Query};
use log::{error, info};
use routes::build_rocket;
use serde::Deserialize;
use std::{env::args, fs::read_to_string, path::PathBuf, process::exit, sync::Arc};

pub mod auth;
pub mod logger;
pub mod request_info;
pub mod responders;
pub mod routes;

#[rocket::main]
async fn main() {
    setup_logger();
    let state = match create_state() {
        Ok(s) => s,
        Err(e) => {
            error!("unable to start: {e:#}");
            exit(1);
        }
    };
    let r = build_rocket(state).launch().await;
    match r {
        Ok(_) => info!("server shutdown"),
        Err(e) => {
            error!("server exited: {e}");
            exit(1);
        }
    }
}

pub struct State {
    pub config: Config,
    pub cache: Arc<Cache>,
    pub database: Arc<dyn Database>,
    pub session_key: SessionKey,
}

#[derive(Debug, Deserialize)]
pub struct Config {
    pub import: jellyimport::Config,
    pub ui: jellyui::Config,
    pub stream: Arc<jellystream::Config>,
    pub session_key: String,
    pub admin_password: String,
    pub asset_path: PathBuf,
    pub database_path: PathBuf,
    pub cache_path: PathBuf,
    pub max_memory_cache_size: usize,
    pub tls: bool,
    pub hostname: String,
}

pub fn create_state() -> Result<Arc<State>> {
    let config_path = args()
        .nth(1)
        .ok_or(anyhow!("first argument (config path) missing"))?;
    let config: Config = serde_yaml_ng::from_str(&read_to_string(config_path)?)?;

    let cache_storage = jellykv::rocksdb::new(&config.cache_path)?;
    let db_storage = jellykv::rocksdb::new(&config.database_path)?;

    let state = Arc::new(State {
        cache: Cache::new(Box::new(cache_storage), config.max_memory_cache_size).into(),
        database: Arc::new(db_storage),
        session_key: SessionKey::parse(&config.session_key)?,
        config,
    });

    create_admin_user(&state)?;

    Ok(state)
}

fn create_admin_user(state: &State) -> Result<()> {
    state.database.transaction(&mut |txn| {
        let admin_row = txn.query_single(Query {
            filter: Filter::Match(Path(vec![USER_LOGIN.0]), "admin".into()),
            ..Default::default()
        })?;
        if admin_row.is_none() {
            info!("Creating new admin user");
            let pwhash = hash_password("admin", &state.config.admin_password);
            txn.insert(
                OBB::new()
                    .with(USER_LOGIN, "admin")
                    .with(USER_PASSWORD, &pwhash)
                    .with(USER_ADMIN, ())
                    .finish(),
            )?;
        }

        Ok(())
    })?;
    Ok(())
}
