use std::io::Read;
use std::sync::Arc;
use tiny_http::{Header, Response, Server, StatusCode};
use ushort::{App, Config, RequestData};

fn main() {
    let arguments: Vec<String> = std::env::args().collect();
    if arguments.len() != 2 {
        eprintln!("Usage: {} <config.toml|config.json>", arguments[0]);
        std::process::exit(1);
    }

    let config = Config::load(&arguments[1]).unwrap_or_else(|error| {
        eprintln!("Configuration error: {error}");
        std::process::exit(1);
    });
    let address = format!("{}:{}", config.host, config.port);
    let app = App::new(config).unwrap_or_else(|error| {
        eprintln!("Startup error: {error}");
        std::process::exit(1);
    });
    let server = Arc::new(Server::http(&address).unwrap_or_else(|error| {
        eprintln!("Failed to listen on {address}: {error}");
        std::process::exit(1);
    }));
    let shutdown_server = Arc::clone(&server);
    ctrlc::set_handler(move || shutdown_server.unblock()).unwrap_or_else(|error| {
        eprintln!("Failed to install signal handler: {error}");
        std::process::exit(1);
    });

    eprintln!("URL Shortener listening on http://{address}");
    eprintln!("Base URL       : {}", app.config.base_url);
    eprintln!(
        "Base path      : {}",
        if app.config.base_path.is_empty() {
            "/"
        } else {
            &app.config.base_path
        }
    );
    eprintln!("DB path        : {}", app.config.db_path.display());
    eprintln!(
        "Short codes    : {}–{} chars",
        app.config.min_short_length, app.config.max_short_length
    );
    eprintln!("Retention days : {}", app.config.retention_days);
    eprintln!(
        "Rate limit     : {} req/{}s per IP",
        app.config.rate_limit_requests, app.config.rate_limit_window
    );
    for mut request in server.incoming_requests() {
        let maximum_body = app.max_request_body_bytes();
        let declared_too_large = request
            .body_length()
            .is_some_and(|length| length > maximum_body);
        let mut body = Vec::new();
        if !declared_too_large {
            let mut reader = request
                .as_reader()
                .take(maximum_body.saturating_add(1) as u64);
            if let Err(error) = reader.read_to_end(&mut body) {
                eprintln!("Failed to read request body: {error}");
                continue;
            }
        }
        let body_too_large = declared_too_large || body.len() > maximum_body;
        let data = RequestData {
            method: request.method().as_str().to_string(),
            target: request.url().to_string(),
            headers: request
                .headers()
                .iter()
                .map(|header| {
                    (
                        header.field.as_str().to_string(),
                        header.value.as_str().into(),
                    )
                })
                .collect(),
            body,
            remote_ip: request
                .remote_addr()
                .map(|address| address.ip().to_string())
                .unwrap_or_default(),
        };
        let client = data.remote_ip.clone();
        let method = data.method.clone();
        let target = data
            .target
            .split_once('?')
            .map_or_else(|| data.target.clone(), |(path, _)| path.to_string());
        let result = if body_too_large {
            ushort::ResponseData::payload_too_large()
        } else {
            app.handle(data)
        };
        let status = result.status;
        let mut response = Response::from_data(result.body).with_status_code(StatusCode(status));
        for (name, value) in result.headers {
            match Header::from_bytes(name.as_bytes(), value.as_bytes()) {
                Ok(header) => response.add_header(header),
                Err(()) => eprintln!("Skipped invalid response header {name}"),
            }
        }
        if let Err(error) = request.respond(response) {
            eprintln!("Failed to respond to {client}: {error}");
        } else {
            eprintln!("{client} - \"{method} {target}\" {status}");
        }
    }
    eprintln!("Shutting down.");
}
