use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};

use anyhow::{Context, Result};
use chainfire_client::Client;
use clap::Parser;
use rcgen::{
    CertificateParams, CertificateSigningRequestParams, DistinguishedName, DnType, Ia5String,
    KeyPair, SanType,
};
use serde::{Deserialize, Serialize};
use tracing::{info, warn};
use tracing_subscriber::EnvFilter;

const DEFAULT_CLUSTER_NAMESPACE: &str = "ultracloud";
const CERT_TTL_DAYS: u64 = 90;
const ROTATION_THRESHOLD_DAYS: u64 = 30;

#[derive(Parser, Debug)]
#[command(author, version, about)]
struct Cli {
    #[arg(long)]
    chainfire_endpoint: String,
    #[arg(long)]
    cluster_id: String,
    #[arg(long, default_value = DEFAULT_CLUSTER_NAMESPACE)]
    cluster_namespace: String,
    #[arg(long)]
    ca_cert_path: PathBuf,
    #[arg(long)]
    ca_key_path: PathBuf,
    #[command(subcommand)]
    command: Command,
}

#[derive(Parser, Debug)]
enum Command {
    /// CA証明書を生成
    InitCa,
    /// CSRから証明書を発行
    Issue {
        #[arg(long)]
        csr_path: PathBuf,
        #[arg(long)]
        cert_path: PathBuf,
        #[arg(long)]
        node_id: Option<String>,
        #[arg(long)]
        service_name: Option<String>,
    },
    /// 証明書のローテーションが必要かチェック
    CheckRotation {
        #[arg(long)]
        cert_path: PathBuf,
    },
}

#[derive(Debug, Serialize, Deserialize)]
struct CertificateBinding {
    node_id: Option<String>,
    service_name: Option<String>,
    cert_serial: String,
    issued_at: u64,
    expires_at: u64,
}

#[tokio::main]
async fn main() -> Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter(EnvFilter::from_default_env().add_directive("info".parse()?))
        .init();

    let cli = Cli::parse();

    match cli.command {
        Command::InitCa => {
            init_ca(&cli.ca_cert_path, &cli.ca_key_path).await?;
        }
        Command::Issue {
            csr_path,
            cert_path,
            node_id,
            service_name,
        } => {
            issue_certificate(
                &cli.chainfire_endpoint,
                &cli.cluster_id,
                &cli.cluster_namespace,
                &cli.ca_cert_path,
                &cli.ca_key_path,
                &csr_path,
                &cert_path,
                node_id,
                service_name,
            )
            .await?;
        }
        Command::CheckRotation { cert_path } => {
            check_rotation(&cert_path).await?;
        }
    }

    Ok(())
}

async fn init_ca(cert_path: &PathBuf, key_path: &PathBuf) -> Result<()> {
    info!("generating CA certificate and key");

    // キーペアを生成
    let key_pair = KeyPair::generate()
        .context("failed to generate CA key pair")?;

    // CA証明書パラメータを設定
    let mut params = CertificateParams::new(vec!["UltraCloud CA".to_string()])
        .context("failed to create certificate params")?;
    
    let mut distinguished_name = DistinguishedName::new();
    distinguished_name.push(DnType::OrganizationName, "UltraCloud");
    distinguished_name.push(DnType::CommonName, "UltraCloud CA");
    params.distinguished_name = distinguished_name;
    params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
    params.key_usages = vec![
        rcgen::KeyUsagePurpose::DigitalSignature,
        rcgen::KeyUsagePurpose::KeyCertSign,
    ];

    // 自己署名CA証明書を生成
    let cert = params.self_signed(&key_pair)
        .context("failed to generate self-signed CA certificate")?;

    // PEM形式で保存
    let cert_pem = cert.pem();
    let key_pem = key_pair.serialize_pem();

    // ディレクトリを作成
    if let Some(parent) = cert_path.parent() {
        std::fs::create_dir_all(parent)
            .with_context(|| format!("failed to create directory for {}", parent.display()))?;
    }
    if let Some(parent) = key_path.parent() {
        std::fs::create_dir_all(parent)
            .with_context(|| format!("failed to create directory for {}", parent.display()))?;
    }

    std::fs::write(cert_path, cert_pem)
        .with_context(|| format!("failed to write CA certificate to {}", cert_path.display()))?;
    std::fs::write(key_path, key_pem)
        .with_context(|| format!("failed to write CA key to {}", key_path.display()))?;

    info!(
        cert_path = %cert_path.display(),
        key_path = %key_path.display(),
        "CA certificate generated successfully"
    );

    Ok(())
}

async fn issue_certificate(
    chainfire_endpoint: &str,
    cluster_id: &str,
    cluster_namespace: &str,
    ca_cert_path: &PathBuf,
    ca_key_path: &PathBuf,
    csr_path: &PathBuf,
    cert_path: &PathBuf,
    node_id: Option<String>,
    service_name: Option<String>,
) -> Result<()> {
    info!("issuing certificate");

    // Chainfireでノード/サービスが許可されているか確認
    if let Some(ref nid) = node_id {
        let mut client = Client::connect(chainfire_endpoint.to_string()).await?;
        let node_key = format!(
            "{}nodes/{}",
            cluster_prefix(cluster_namespace, cluster_id),
            nid
        );
        let node_data = client.get(&node_key.as_bytes()).await?;
        if node_data.is_none() {
            anyhow::bail!("node {} not found in Chainfire", nid);
        }
    }

    // CA証明書とキーを読み込み
    let ca_cert_pem = std::fs::read_to_string(ca_cert_path)
        .with_context(|| format!("failed to read CA cert from {}", ca_cert_path.display()))?;
    let ca_key_pem = std::fs::read_to_string(ca_key_path)
        .with_context(|| format!("failed to read CA key from {}", ca_key_path.display()))?;
    let csr_pem = std::fs::read_to_string(csr_path)
        .with_context(|| format!("failed to read CSR from {}", csr_path.display()))?;

    // CAキーペアを読み込み
    let ca_key_pair = KeyPair::from_pem(&ca_key_pem)
        .context("failed to parse CA key pair from PEM")?;

    // 既存のCA証明書からパラメータを復元（Subject/Validity/SKIなど）
    let ca_params = CertificateParams::from_ca_cert_pem(&ca_cert_pem)
        .context("failed to parse CA certificate")?;

    // 署名用CA証明書オブジェクトを作成（パラメータは既存CAに合わせる）
    let ca_cert = ca_params
        .self_signed(&ca_key_pair)
        .context("failed to build CA certificate for signing")?;

    let mut csr_params = CertificateSigningRequestParams::from_pem(&csr_pem)
        .context("failed to parse CSR")?;

    // Ensure expected SANs are present (best-effort)
    if let Some(ref nid) = node_id {
        ensure_dns_san(&mut csr_params.params, &format!("node-{}", nid));
    }
    if let Some(ref svc) = service_name {
        ensure_dns_san(&mut csr_params.params, svc);
    }
    if csr_params.params.subject_alt_names.is_empty() {
        ensure_dns_san(&mut csr_params.params, "ultracloud-service");
    }

    // CA署名証明書を生成（CSRの公開鍵を利用）
    let cert = csr_params
        .signed_by(&ca_cert, &ca_key_pair)
        .context("failed to sign CSR with CA")?;

    let cert_pem = cert.pem();
    // ディレクトリを作成
    if let Some(parent) = cert_path.parent() {
        std::fs::create_dir_all(parent)
            .with_context(|| format!("failed to create directory for {}", parent.display()))?;
    }

    // 証明書とキーを保存
    std::fs::write(cert_path, cert_pem)
        .with_context(|| format!("failed to write certificate to {}", cert_path.display()))?;
    info!(
        cert_path = %cert_path.display(),
        "CSR-signed certificate saved (private key remains with requester)"
    );

    // Chainfireに証明書バインディングを記録
    let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();

    // 証明書のシリアル番号と有効期限を取得（DERから抽出）
    let (cert_serial, expires_at) = {
        let cert_der = cert.der();
        let (_, parsed) = x509_parser::parse_x509_certificate(cert_der.as_ref())
            .map_err(|e| anyhow::anyhow!("failed to parse issued cert: {:?}", e))?;
        let serial = parsed.tbs_certificate.raw_serial();
        let serial_hex = serial
            .iter()
            .map(|b| format!("{:02x}", b))
            .collect::<String>();
        let validity = parsed.validity();
        let not_after = validity.not_after.timestamp();
        let not_after = if not_after > 0 {
            not_after as u64
        } else {
            now + (CERT_TTL_DAYS * 24 * 3600)
        };
        (serial_hex, not_after)
    };

    let mut client = Client::connect(chainfire_endpoint.to_string()).await?;
    let binding = CertificateBinding {
        node_id: node_id.clone(),
        service_name: service_name.clone(),
        cert_serial,
        issued_at: now,
        expires_at,
    };
    let binding_key = if let Some(ref nid) = node_id {
        format!(
            "{}mtls/certs/nodes/{}",
            cluster_prefix(cluster_namespace, cluster_id),
            nid
        )
    } else if let Some(ref svc) = service_name {
        format!(
            "{}mtls/certs/services/{}",
            cluster_prefix(cluster_namespace, cluster_id),
            svc
        )
    } else {
        format!(
            "{}mtls/certs/unknown",
            cluster_prefix(cluster_namespace, cluster_id)
        )
    };
    let binding_value = serde_json::to_vec(&binding)?;
    client.put(&binding_key.as_bytes(), &binding_value).await?;

    info!(
        cert_path = %cert_path.display(),
        node_id = ?node_id,
        service_name = ?service_name,
        "certificate issued and recorded in Chainfire"
    );

    Ok(())
}

fn ensure_dns_san(params: &mut CertificateParams, name: &str) {
    let exists = params.subject_alt_names.iter().any(|san| match san {
        SanType::DnsName(dns) => dns.as_ref() == name,
        _ => false,
    });
    if !exists {
        if let Ok(ia5_name) = Ia5String::try_from(name.to_string()) {
            params.subject_alt_names.push(SanType::DnsName(ia5_name));
        } else {
            tracing::warn!("Invalid DNS name for SAN: {}", name);
        }
    }
}

async fn check_rotation(cert_path: &PathBuf) -> Result<()> {
    let cert_pem = std::fs::read_to_string(cert_path)
        .with_context(|| format!("failed to read certificate from {}", cert_path.display()))?;

    // 証明書をDER形式に変換
    let cert_der_vec = rustls_pemfile::certs(&mut cert_pem.as_bytes())
        .collect::<Result<Vec<_>, _>>()
        .context("failed to parse certificate from PEM")?;
    let cert_der = cert_der_vec.first()
        .context("no certificate found in PEM file")?;

    // x509-parserを使って証明書をパース
    let (_, cert) = x509_parser::parse_x509_certificate(cert_der.as_ref())
        .map_err(|e| anyhow::anyhow!("failed to parse X.509 certificate: {:?}", e))?;

    let validity = cert.validity();
    let not_after = validity.not_after.timestamp();
    let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64;
    let days_until_expiry = (not_after - now) / 86400;

    if days_until_expiry < ROTATION_THRESHOLD_DAYS as i64 {
        warn!(
            cert_path = %cert_path.display(),
            days_until_expiry = days_until_expiry,
            threshold = ROTATION_THRESHOLD_DAYS,
            "certificate should be rotated soon"
        );
        return Ok(());
    }

    info!(
        cert_path = %cert_path.display(),
        days_until_expiry = days_until_expiry,
        "certificate is still valid"
    );

    Ok(())
}

fn cluster_prefix(cluster_namespace: &str, cluster_id: &str) -> String {
    format!("{}/clusters/{}/", cluster_namespace, cluster_id)
}
