#![feature(cmp_minmax)]

mod collection;
mod collection_mutexes;
mod error;
mod metrics;
mod point;
mod protocol;
mod reports;
mod server;
mod snapshots;
mod state;
mod storage;

use anyhow::Result;
use clap::Parser;
use colored::{ColoredString, Colorize};
use env_logger::Env;
use sqlx::types::chrono;
use std::{
    fmt::Debug,
    io::Write,
    time::{Duration, SystemTime},
};

#[derive(Parser, Debug)]
#[command(
    name = "qdrant-api",
    version,
    arg_required_else_help(true),
    disable_help_flag(true),
    about = "Qdrant API Server for Qdrant on TiDB Serverless."
)]
struct Args {
    #[arg(
        id = "host",
        short = 'h',
        long = "host",
        env = "DB_HOST",
        default_value = "localhost",
        help = "Sets the host for the MySQL server",
        num_args(1)
    )]
    host: String,
    #[arg(
        id = "user",
        short = 'u',
        long = "user",
        env = "DB_USERNAME",
        default_value = "root",
        help = "Sets the username for the MySQL server",
        num_args(1)
    )]
    user: String,
    #[arg(
        id = "password",
        short = 'p',
        long = "password",
        env = "DB_PASSWORD",
        default_value = "",
        help = "Sets the password for the MySQL server",
        num_args(1)
    )]
    password: String,
    #[arg(
        id = "database",
        short = 'D',
        long = "database",
        env = "DB_DATABASE",
        help = "Sets the database for the MySQL server",
        num_args(1),
        required(true)
    )]
    database: String,
    #[arg(
        id = "cert",
        long = "cert",
        env = "CERT_PATH",
        default_value = "tls.crt",
        help = "Certificate file in PEM format.",
        num_args(1)
    )]
    tls_cert: String,
    #[arg(
        id = "key",
        long = "key",
        env = "KEY_PATH",
        default_value = "tls.key",
        help = "X509 key in PEM format.",
        num_args(1)
    )]
    tls_key: String,
    #[arg(
        id = "min-connections",
        long = "min-connections",
        env = "MIN_CONNECTIONS",
        default_value = "1",
        help = "Minimum number of connections",
        num_args(1)
    )]
    min_connection: u32,
    #[arg(
        id = "max-connections",
        long = "max-connections",
        env = "MAX_CONNECTIONS",
        default_value = "50",
        help = "Maximum number of connections",
        num_args(1)
    )]
    max_connection: u32,
    #[arg(
        id = "idle-timeout",
        long = "idle-timeout",
        env = "IDLE_TIMEOUT",
        default_value = "60",
        help = "Maximum idle duration for individual connections in seconds.",
        num_args(1)
    )]
    idle_timeout: u64,
    #[arg(
        id = "max-lifetime",
        long = "max-lifetime",
        env = "MAX_LIFETIME",
        default_value = "300",
        help = "Maximum lifetime of individual connections in seconds.",
        num_args(1)
    )]
    max_lifetime: u64,
    #[arg(
        id = "search-service",
        long = "search-service",
        env = "SEARCH_SERVICE",
        default_value = "dummy:///?version=0&index=shard0",
        help = "Endpoint of FTS Serving Service.",
        num_args(1)
    )]
    search_service: String,
    #[arg(
        id = "qdrant-service",
        long = "qdrant-service",
        env = "QDRANT_SERVICE",
        num_args(1)
    )]
    qdrant_service: Option<String>,
}

impl Default for Args {
    fn default() -> Self {
        Self {
            host: std::env::var("DB_HOST").unwrap_or_else(|_| "localhost".to_string()),
            user: std::env::var("DB_USERNAME").unwrap_or_else(|_| "abc.root".to_string()),
            password: std::env::var("DB_PASSWORD").unwrap_or_else(|_| "xxxx".to_string()),
            database: std::env::var("DB_DATABASE").unwrap_or_else(|_| "ai".to_string()),
            tls_cert: std::env::var("TLS_CERT").unwrap_or_else(|_| "cert.pem".to_string()),
            tls_key: std::env::var("TLS_KEY").unwrap_or_else(|_| "key.pem".to_string()),
            min_connection: std::env::var("MIN_CONNECTIONS")
                .unwrap_or_else(|_| "1".to_string())
                .parse()
                .unwrap(),
            max_connection: std::env::var("MAX_CONNECTIONS")
                .unwrap_or_else(|_| "50".to_string())
                .parse()
                .unwrap(),
            idle_timeout: std::env::var("IDLE_TIMEOUT")
                .unwrap_or_else(|_| "60".to_string())
                .parse()
                .unwrap(),
            max_lifetime: std::env::var("MAX_LIFETIME")
                .unwrap_or_else(|_| "300".to_string())
                .parse()
                .unwrap(),
            search_service: std::env::var("SEARCH_SERVICE")
                .unwrap_or_else(|_| "dummy:///?version=0&index=shard0".to_string()),
            qdrant_service: None,
        }
    }
}

#[tokio::main]
#[allow(clippy::needless_return)]
async fn main() -> Result<()> {
    env_logger::Builder::from_env(Env::default().default_filter_or("info"))
        .write_style(env_logger::WriteStyle::Auto)
        .format(|buf, record| {
            writeln!(
                buf,
                "[{} {} {}:{}] {}",
                chrono::DateTime::<chrono::Utc>::from(
                    SystemTime::now()
                        .checked_add(Duration::from_secs(8 * 3600))
                        .unwrap()
                )
                .format("%Y-%m-%d %H:%M:%S"),
                level_with_style(record.level()),
                record.file().unwrap_or("unknown"),
                record.line().unwrap_or(0),
                record.args()
            )
        })
        .init();

    server::serve(Args::parse()).await
}

fn level_with_style(level: log::Level) -> ColoredString {
    match level {
        log::Level::Error => level.as_str().bold().red(),
        log::Level::Warn => level.as_str().bold().yellow(),
        log::Level::Info => level.as_str().bold().green(),
        log::Level::Debug => level.as_str().bold().blue(),
        log::Level::Trace => level.as_str().bold().purple(),
    }
}
