use std::fs;
use std::path::PathBuf;
use std::process;
use std::sync::Arc;

use async_trait::async_trait;
use dockerfile_builder::Dockerfile;
use dockerfile_builder::instruction::{CMD, COPY, FROM, RUN, USER, WORKDIR};
use dockerfile_builder::instruction_builder::CopyBuilder;
use log::{debug, info};
use serde::Serialize;

use crate::config::{REGISTRY_PROJECT, REGISTRY_URL};
use crate::{
    score::Score,
    topology::{Topology, Url},
};

use super::{Application, ApplicationFeature, ApplicationInterpret, OCICompliant};

#[derive(Debug, Serialize, Clone)]
pub struct RustWebappScore<T: Topology + Clone + Serialize> {
    pub name: String,
    pub domain: Url,
    pub features: Vec<Box<dyn ApplicationFeature<T>>>,
    pub application: RustWebapp,
}

impl<T: Topology + std::fmt::Debug + Clone + Serialize + 'static> Score<T> for RustWebappScore<T> {
    fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> {
        Box::new(ApplicationInterpret {
            features: self.features.clone(),
            application: Arc::new(Box::new(self.application.clone())),
        })
    }

    fn name(&self) -> String {
        format!("{}-RustWebapp", self.name)
    }
}

#[derive(Debug, Clone, Serialize)]
pub enum RustWebFramework {
    Leptos,
}

#[derive(Debug, Clone, Serialize)]
pub struct RustWebapp {
    pub name: String,
    /// The path to the root of the Rust project to be containerized.
    pub project_root: PathBuf,
    pub framework: Option<RustWebFramework>,
}

impl Application for RustWebapp {
    fn name(&self) -> String {
        self.name.clone()
    }
}

#[async_trait]
impl OCICompliant for RustWebapp {
    /// Builds a Docker image for the Rust web application using a multi-stage build,
    /// pushes it to the configured OCI registry, and returns the full image tag.
    async fn build_push_oci_image(&self) -> Result<String, String> {
        // This function orchestrates the build and push process.
        // It's async to match the trait definition, though the underlying docker commands are blocking.
        info!("Starting OCI image build and push for '{}'", self.name);

        // 1. Build the local image by calling the synchronous helper function.
        let local_image_name = self
            .build_docker_image()
            .map_err(|e| format!("Failed to build Docker image: {}", e))?;
        info!(
            "Successfully built local Docker image: {}",
            local_image_name
        );

        // 2. Push the image to the registry.
        let remote_image_name = self
            .push_docker_image(&local_image_name)
            .map_err(|e| format!("Failed to push Docker image: {}", e))?;
        info!("Successfully pushed Docker image to: {}", remote_image_name);

        Ok(remote_image_name)
    }
}

/// Implementation of helper methods for building and pushing the Docker image.
impl RustWebapp {
    /// Generates a multi-stage Dockerfile for a Rust application.
    fn build_dockerfile(&self) -> Result<PathBuf, Box<dyn std::error::Error>> {
        let mut dockerfile = Dockerfile::new();

        self.build_builder_image(&mut dockerfile);

        // --- Stage 2: Final Image ---
        // Use a minimal, non-Alpine base image for the final container.
        dockerfile.push(FROM::from("debian:bullseye-slim"));

        // Create a non-root user for security.
        dockerfile.push(RUN::from(
            "groupadd -r appgroup && useradd -r -s /bin/false -g appgroup appuser",
        ));

        // Copy only the compiled binary from the builder stage.
        let binary_path_in_builder = format!("/app/target/release/{}", self.name);
        let binary_path_in_final = format!("/usr/local/bin/{}", self.name);
        dockerfile.push(
            CopyBuilder::builder()
                .from("builder")
                .src(binary_path_in_builder)
                .dest(&binary_path_in_final)
                .build()
                .unwrap(),
        );

        // Run as the non-root user.
        dockerfile.push(USER::from("appuser"));

        // Set the command to run the application.
        dockerfile.push(CMD::from(binary_path_in_final));

        // Save the Dockerfile to a uniquely named file in the project root to avoid conflicts.
        let dockerfile_path = self.project_root.join("Dockerfile.harmony");
        fs::write(&dockerfile_path, dockerfile.to_string())?;

        Ok(dockerfile_path)
    }

    /// Builds the Docker image using the generated Dockerfile.
    pub fn build_docker_image(&self) -> Result<String, Box<dyn std::error::Error>> {
        info!("Generating Dockerfile for '{}'", self.name);
        let dockerfile_path = self.build_dockerfile()?;

        info!(
            "Building Docker image with file {} from root {}",
            dockerfile_path.to_string_lossy(),
            self.project_root.to_string_lossy()
        );
        let image_name = format!("{}-webapp", self.name);

        let output = process::Command::new("docker")
            .args([
                "build",
                "--file",
                dockerfile_path.to_str().unwrap(),
                "-t",
                &image_name,
                self.project_root.to_str().unwrap(),
            ])
            .spawn()?
            .wait_with_output()?;

        self.check_output(&output, "Failed to build Docker image")?;

        Ok(image_name)
    }

    /// Tags and pushes a Docker image to the configured remote registry.
    fn push_docker_image(&self, image_name: &str) -> Result<String, Box<dyn std::error::Error>> {
        let full_tag = format!("{}/{}/{}", *REGISTRY_URL, *REGISTRY_PROJECT, &image_name);
        info!("Pushing docker image {full_tag}");

        // Tag the image for the remote registry.
        let output = process::Command::new("docker")
            .args(["tag", image_name, &full_tag])
            .output()?;
        self.check_output(&output, "Tagging docker image failed")?;
        debug!(
            "docker tag output: stdout: {}, stderr: {}",
            String::from_utf8_lossy(&output.stdout),
            String::from_utf8_lossy(&output.stderr)
        );
        todo!("Are we good?");

        // Push the image.
        let output = process::Command::new("docker")
            .args(["push", &full_tag])
            .output()?;
        self.check_output(&output, "Pushing docker image failed")?;
        debug!(
            "docker push output: stdout: {}, stderr: {}",
            String::from_utf8_lossy(&output.stdout),
            String::from_utf8_lossy(&output.stderr)
        );

        Ok(full_tag)
    }

    /// Checks the output of a process command for success.
    fn check_output(
        &self,
        output: &process::Output,
        msg: &str,
    ) -> Result<(), Box<dyn std::error::Error>> {
        if !output.status.success() {
            let error_message = format!("{}: {}", msg, String::from_utf8_lossy(&output.stderr));
            return Err(error_message.into());
        }
        Ok(())
    }

    fn build_builder_image(&self, dockerfile: &mut Dockerfile) {
        match self.framework {
            Some(RustWebFramework::Leptos) => {todo!(r#"
            # Get started with a build env with Rust nightly
FROM rustlang/rust:nightly-bookworm as builder

# If you’re using stable, use this instead
# FROM rust:1.86-bullseye as builder

# Install cargo-binstall, which makes it easier to install other
# cargo extensions like cargo-leptos
RUN wget https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz
RUN tar -xvf cargo-binstall-x86_64-unknown-linux-musl.tgz
RUN cp cargo-binstall /usr/local/cargo/bin

# Install required tools
RUN apt-get update -y \
  && apt-get install -y --no-install-recommends clang

# Install cargo-leptos
RUN cargo binstall cargo-leptos -y

# Add the WASM target
RUN rustup target add wasm32-unknown-unknown

# Make an /app dir, which everything will eventually live in
RUN mkdir -p /app
WORKDIR /app
COPY . .

# Build the app
RUN cargo leptos build --release -vv
                "#)}
            None => {
                // --- Stage 1: Builder ---
                // Use the official Rust image as the build environment.
                dockerfile.push(FROM::from("rust:latest as builder"));

                // Install the wasm32 target as required.
                dockerfile.push(RUN::from("rustup target add wasm32-unknown-unknown"));
                dockerfile.push(WORKDIR::from("/app"));

                // Copy the source code and build the application.
                dockerfile.push(COPY::from(". ."));
                dockerfile.push(RUN::from("cargo build --release --locked"));
            }
        }
    }
}
