use std::fs;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use syn::{visit, visit::Visit, BinOp, ItemFn};

/// Configuration loaded from `mutant.toml`.
///
/// Stores credentials required for communicating with the LLM provider.
#[derive(Deserialize)]
struct Config {
    api_key: String,
}
/// Loads mutation framework configuration.
///
/// Reads and parses the `mutant.toml` file located in the current
/// working directory.
///
/// # Expected Format
///
/// ```toml
/// api_key = "your-api-key"
/// ```
///
/// # Errors
///
/// Returns an error if:
///
/// - The file cannot be read.
/// - The file contains invalid TOML.
/// - Required fields are missing.
fn load_config() -> Result<Config, Box<dyn std::error::Error>> {
    let content = fs::read_to_string("mutant.toml")?;
    let config: Config = toml::from_str(&content)?;
    Ok(config)
}
/// Structural metrics extracted from a Rust function.
///
/// These metrics are provided to the language model to help determine
/// an appropriate mutation-testing strategy.
///
/// The collected values describe the complexity and operator usage of
/// the analyzed function.
#[derive(Debug, Serialize, Default)]
pub struct CodeFeatures {
    /// Number of arithmetic operations.
    ///
    /// Examples:
    ///
    /// ```rust
    /// a + b
    /// x * y
    /// ```
    pub arithmetic_ops: usize,

    /// Number of relational comparisons.
    ///
    /// Examples:
    ///
    /// ```rust
    /// a < b
    /// x >= y
    /// ```
    pub relational_ops: usize,

    /// Number of logical operations.
    ///
    /// Examples:
    ///
    /// ```rust
    /// a && b
    /// a || b
    /// ```
    pub logical_ops: usize,

    /// Number of bitwise and shift operations.
    ///
    /// Examples:
    ///
    /// ```rust
    /// a & b
    /// a << b
    /// ```
    pub bitwise_ops: usize,

    /// Number of function calls.
    ///
    /// Includes the root function being analyzed.
    pub function_count: usize,

    /// Number of function parameters.
    pub parameter_count: usize,

    /// Number of source lines.
    ///
    /// Currently reserved for future use.
    pub line_count: usize,
}
/// Extracts metrics from a Rust function.
///
/// Traverses the function syntax tree and records operator usage and
/// structural complexity.
///
/// # Parameters
///
/// * `func` - Function to analyze.
///
/// # Returns
///
/// A populated [`CodeFeatures`] instance.
impl CodeFeatures {
    pub fn new(func: &ItemFn) -> CodeFeatures {
        let mut visitor = FeatureVisitor {
            features: CodeFeatures::default(),
        };

        visitor.features.function_count = 1;

        visitor.features.parameter_count =
            func.sig.inputs.len();

        visitor.visit_item_fn(func);

        visitor.features
    }
}
/// AST visitor used to collect function metrics.
///
/// Traverses the syntax tree and updates a [`CodeFeatures`] instance.
struct FeatureVisitor {
    features: CodeFeatures,
}

impl<'ast> Visit<'ast> for FeatureVisitor {
    fn visit_expr_binary(
        &mut self,
        node: &'ast syn::ExprBinary,
    ) {
        match &node.op {
            // Arithmetic
            BinOp::Add(_)
            | BinOp::Sub(_)
            | BinOp::Mul(_)
            | BinOp::Div(_)
            | BinOp::Rem(_) => {
                self.features.arithmetic_ops += 1;
            }

            // Relational
            BinOp::Eq(_)
            | BinOp::Ne(_)
            | BinOp::Lt(_)
            | BinOp::Le(_)
            | BinOp::Gt(_)
            | BinOp::Ge(_) => {
                self.features.relational_ops += 1;
            }

            // Logical
            BinOp::And(_)
            | BinOp::Or(_) => {
                self.features.logical_ops += 1;
            }

            // Bitwise + Shift
            BinOp::BitAnd(_)
            | BinOp::BitOr(_)
            | BinOp::BitXor(_)
            | BinOp::Shl(_)
            | BinOp::Shr(_) => {
                self.features.bitwise_ops += 1;
            }

            _ => {}
        }

        visit::visit_expr_binary(self, node);
    }

    fn visit_expr_call(
        &mut self,
        node: &'ast syn::ExprCall,
    ) {
        self.features.function_count += 1;

        visit::visit_expr_call(self, node);
    }
}
/// Mutation strategy generated by the language model.
///
/// This structure is produced from the JSON response returned by the
/// AI provider and is later converted into a mutation-testing
/// configuration.
#[derive(Debug, Deserialize)]
pub struct MutationPlan {
    /// Selected mutation operators.
    ///
    /// Example:
    ///
    /// ```json
    /// ["AOR", "ROR", "LOR"]
    /// ```
    pub operators: Vec<String>,

    /// Number of mutants generated per operator.
    pub mutation_count: usize,

    /// Number of mutation combinations.
    pub combination_count: usize,

    /// Probability that a mutation will be applied.
    ///
    /// Expected range:
    ///
    /// ```text
    /// 0.0 ..= 1.0
    /// ```
    pub mutation_chance: f64,

    /// Required mutation score percentage.
    pub acceptable_score: f64,
}
/// Builds a mutation-planning prompt for the language model.
///
/// Converts extracted code metrics into a structured prompt and
/// requests a JSON mutation strategy.
///
/// # Parameters
///
/// * `features` - Extracted code metrics.
///
/// # Returns
///
/// A complete prompt ready to be sent to the LLM.
pub fn build_prompt(features: &CodeFeatures) -> String {
    format!(
        r#"
        You are a mutation testing expert.

        Given these Rust code metrics:

        {}

        Choose mutation operators and mutation settings.

        Allowed operators:
        AOD, AOI, AOR,
        COD, COI, COR,
        LOD, LOI, LOR,
        ROR, SDL, SOR,
        IPVR, IPEX, IMCD

        Use only these values.

        Return ONLY JSON:

        {{
          "operators": ["ROR","LOR"],
          "mutation_count": 50,
          "combination_count": 10,
          "mutation_chance": 0.8,
          "acceptable_score": 90.0
        }}
        "#,
        serde_json::to_string_pretty(features).unwrap()
    )
}
/// Requests a mutation strategy from the configured language model.
///
/// The function:
///
/// 1. Loads API credentials.
/// 2. Creates an OpenRouter request.
/// 3. Sends the generated prompt.
/// 4. Parses the returned JSON.
/// 5. Produces a [`MutationPlan`].
///
/// # Parameters
///
/// * `prompt` - Prompt generated by [`build_prompt`].
///
/// # Returns
///
/// A mutation plan generated by the language model.
///
/// # Panics
///
/// This function currently panics if:
///
/// - Configuration loading fails.
/// - The HTTP request fails.
/// - The response cannot be parsed.
/// - The returned JSON is invalid.
/// - The model returns content that does not match
///   [`MutationPlan`].
///
/// # Notes
///
/// The implementation currently uses:
///
/// ```text
/// https://openrouter.ai/api/v1/chat/completions
/// ```
///
/// and executes async requests through a temporary Tokio runtime.
///
/// # Example
///
/// ```rust,no_run
/// let features = CodeFeatures::new(&func);
///
/// let prompt = build_prompt(&features);
///
/// let plan = ask_llm(prompt);
///
/// println!("{:?}", plan);
/// ```
pub fn ask_llm(
    prompt: String,
) -> MutationPlan{
    let rt = tokio::runtime::Runtime::new().unwrap();
    let client = Client::new();

    let config = load_config().expect("Error loading config");

    let response = rt.block_on(async {client
        .post("https://openrouter.ai/api/v1/chat/completions")
        .bearer_auth(config.api_key)
        .json(&serde_json::json!({
            "model": "deepseek",
            "messages": [
                {
                    "role": "user",
                    "content": prompt
                }
            ]
        }))
        .send()
        .await.expect("Failed to send request")});

    let body: serde_json::Value = rt.block_on(async { response.json().await.expect("failed to parse response")});

    let content = body["choices"][0]["message"]["content"]
        .as_str()
        .unwrap();

    let plan: MutationPlan =
        serde_json::from_str(content).expect("failed to parse response");

    plan
}