use awc::{Client, ClientBuilder};
use actix_web::{web, App, HttpResponse, HttpServer, Responder, HttpRequest, HttpResponseBuilder};
use awc::http::header::{HeaderMap, HeaderName, HeaderValue};
use act;
use actix_web::web::{Bytes};
use std::fs::File;
use std::io::{BufRead, BufReader};
use actix_web::web::Payload;
use log::{info, warn, error};
use env_logger;
use bytes::{BytesMut};
use futures_util::stream::StreamExt;
use std::future::Future;
use std::time::Duration;
use std::io::Read;
use futures_util::TryStreamExt;

#[derive(Clone, Debug)]
struct FirewallRule {
    conditions: Vec<(String, String)>,
    operator: String,
    action: String,
}

fn readlinesfirewall() -> Vec<FirewallRule> {
    let file = match File::open("firewall.txt") {
        Ok(file) => file,
        Err(e) => {
            error!("Failed to open firewall.txt: {:?}", e);
            return Vec::new();
        }
    };
    let reader = BufReader::new(file);
    let mut rules = Vec::new();

    for line in reader.lines() {
        if let Ok(line) = line {
            let parts: Vec<&str> = line.splitn(2, ')').collect();
            if parts.len() == 2 {
                let conditions_str = parts[0].trim().trim_start_matches('(');
                let action = parts[1].trim().to_lowercase();

                let mut conditions = Vec::new();
                let main_parts: Vec<&str> = conditions_str.split("||").collect();

                for part in main_parts {
                    let sub_parts: Vec<&str> = part.split("&&").collect();
                    for condition in sub_parts {
                        if let Some((key, value)) = condition.trim().split_once(':') {
                            let key = key.trim().to_lowercase();
                            conditions.push((key, value.trim().to_string()));
                        }
                    }
                }

                let operator = if conditions_str.contains("||") {
                    "||"
                } else if conditions_str.contains("&&") {
                    "&&"
                } else {
                    ""
                };

                if !conditions.is_empty() {
                    rules.push(FirewallRule {conditions, operator: operator.to_string(), action });
                }
            }
        }
    }

    rules
}

async fn send_request_to_backend(req: HttpRequest, payload: web::Payload) -> HttpResponse {
    let base_url = "https://lmarena.ai";
    let path = req.uri().path_and_query().map(|x| x.as_str()).unwrap_or("");
    let target_url = format!("{}{}", base_url, path);

    // Create HTTP client
    let client = Client::builder().timeout(Duration::from_secs(10)).finish();

    // Prepare the request builder with the same method as the incoming request
    let mut forward_request = client.request(req.method().clone(), &target_url);

    // Forward headers from the original request
    for (header_name, header_value) in req.headers() {
        if !["host", "connection"].contains(&header_name.as_str()) {
            forward_request = forward_request.insert_header((header_name.clone(), header_value.clone()));
        }
    }

    // Send the request, streaming the body
    match forward_request.send_stream(payload).await {
        Ok(mut res) => {
            let mut client_resp = HttpResponse::build(res.status());

            // Forward response headers
            for (header_name, header_value) in res.headers().iter().filter(|(h, _)| *h != "connection") {
                client_resp.insert_header((header_name.clone(), header_value.clone()));
            }

            // Stream the response body
            client_resp.streaming(res)
        }
        Err(e) => {
            error!("Failed to forward request: {:?}", e);
            HttpResponse::InternalServerError().body(format!("Failed to forward request: {:?}", e))
        }
    }
}

async fn handle_request(req: HttpRequest, payload: web::Payload, firewall_rules: web::Data<Vec<FirewallRule>>) -> HttpResponse {
    // Checking cookie
    if let Some(cookie) = req.cookie("waiting_done") {
        if !cookie.value().is_empty() {
            return send_request_to_backend(req, payload).await;
        }
    }

    // No valid cookie, check firewall rules
    let (is_blocked, should_set_cookie, blocked_rule) = firewall(&req, &firewall_rules).await;

    if is_blocked {
        // Request is blocked by firewall
        let block_message = match blocked_rule {
            Some(rule_index) => format!("Request was blocked by firewall rule {}", rule_index),
            None => "Request was blocked by firewall".to_string(),
        };
        HttpResponse::Forbidden().body(block_message)
    } else if should_set_cookie {
        // Only show waiting page for HTML requests
        if req.headers().get("accept").map_or(false, |h| h.to_str().unwrap_or("").contains("text/html")) {
            let html_content = r#"
                <!DOCTYPE html>
                <html lang="en">
                <head>
                    <meta charset="UTF-8">
                    <meta name="viewport" content="width=device-width, initial-scale=1.0">
                    <title>Waiting</title>
                        <script>
                        function setCookie(name, value, days) {
                            var expires = "";
                            if (days) {
                                var date = new Date();
                                date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
                                expires = "; expires=" + date.toUTCString();
                            }
                            document.cookie = name + "=" + (value || "")  + expires + "; path=/";
                        }

                        function redirectAfterDelay() {
                            setTimeout(() => {
                                setCookie('waiting_done', 'true', 1);
                                window.location.reload();
                            }, 2000);
                        }
                    </script>
                </head>
                <body onload="redirectAfterDelay()">
                    <h1>Wait two seconds...</h1>
                </body>
                </html>
            "#;

            HttpResponse::Ok()
                .content_type("text/html; charset=utf-8")
                .body(html_content)
        } else {
            // For non-HTML requests, forward immediately
            send_request_to_backend(req, payload).await
        }
    } else {
        // Request needs to be checked again
        HttpResponse::Ok().body("Please try again")
    }
}

async fn firewall(req: &HttpRequest, firewall_rules: &Vec<FirewallRule>) -> (bool, bool, Option<usize>) {
    info!("Received request with headers:");
    for (header, value) in req.headers() {
        info!("{}: {:?}", header, value);
    }

    for (index, rule) in firewall_rules.iter().enumerate() {
        info!("Checking rule {}: {:?}", index + 1, rule);

        let should_block = if rule.operator == "||" {
            rule.conditions.iter().any(|(header, value)| check_condition(req, header, value))
        } else {
            rule.conditions.iter().all(|(header, value)| check_condition(req, header, value))
        };

        info!("Rule {} should block: {}", index + 1, should_block);

        if should_block {
            info!("Rule {} matched: {:?}", index + 1, rule);
            if rule.action == "block" {
                warn!("Request blocked by firewall rule {}", index + 1);
                return (true, false, Some(index + 1));
            }
        }
    }
    info!("Request passed firewall check");
    (false, true, None)
}

fn check_condition(req: &HttpRequest, header: &str, value: &str) -> bool {
    let header_value = req.headers().get(header);

    match header_value {
        Some(req_value) => {
            let req_value_str = req_value.to_str().unwrap_or("");
            info!("Checking header: {}, value: {}, against: {}", header, req_value_str, value);
            req_value_str.to_lowercase().contains(&value.to_lowercase())
        },
        None => {
            info!("Header not found: {}, considering as not matching", header);
            false
        }
    }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));

    let firewall_rules = web::Data::new(readlinesfirewall());

    info!("Loaded firewall rules: {:?}", firewall_rules);

    HttpServer::new(move || {
        App::new()
            .app_data(firewall_rules.clone())
            .default_service(web::to(handle_request))
    })
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}