// synops-mail — Send og motta epost for Synops.
//
// Send-modus: sender epost via msmtp (Brevo SMTP-relay).
// Motta-modus: tar imot epost fra Postfix via pipe transport.
//   - Sjekker at avsender-epost finnes i auth_identities
//   - Sjekker at innholdet starter med en konfigurerbar frase (default: "Kjære vaktmester")
//   - Oppretter content-node i brukerens innboks ved match
//   - Alt annet → /dev/null, ingen bounce
//
// Bruk:
//   synops-mail --send --to bruker@eksempel.no --subject "Emne"
//   echo "Brødtekst" | synops-mail --send --to bruker@eksempel.no --subject "Emne"
//   synops-mail --send --to bruker@eksempel.no --subject "Emne" --body "Tekst her"
//   cat raw_email.eml | synops-mail --receive --recipient bruker@synops.no --sender avsender@test.no
//
// Output: Bekreftelse til stdout ved suksess.
// Feil: stderr + exit code != 0.
//
// Konfigurering:
//   msmtp-konfig: /srv/synops/config/msmtp/msmtprc
//   Miljøvariabel MSMTP_CONFIG kan overstyre config-sti.
//   Miljøvariabel SYNOPS_MAIL_PHRASE kan overstyre aktiveringsfrase (default: "Kjære vaktmester").
//
// Ref: docs/retninger/unix_filosofi.md

use clap::Parser;
use std::io::{self, Read};
use std::process::{Command, Stdio};

const DEFAULT_FROM: &str = "vaktmester@synops.no";
const DEFAULT_FROM_NAME: &str = "Synops Vaktmester";
const DEFAULT_CONFIG: &str = "/srv/synops/config/msmtp/msmtprc";
// Frase-sjekk fjernet — avsender-verifisering via auth_identities er nok.

/// Synops epost-verktøy: send og motta epost.
#[derive(Parser)]
#[command(name = "synops-mail", about = "Send og motta epost for Synops")]
struct Cli {
    /// Send-modus
    #[arg(long)]
    send: bool,

    /// Motta-modus (fra Postfix pipe transport)
    #[arg(long)]
    receive: bool,

    /// Mottaker-epostadresse (--send)
    #[arg(long)]
    to: Option<String>,

    /// Emne (--send)
    #[arg(long)]
    subject: Option<String>,

    /// Brødtekst (--send, valgfritt — leses fra stdin om ikke oppgitt)
    #[arg(long)]
    body: Option<String>,

    /// Avsender (--send default: vaktmester@synops.no, --receive: envelope sender)
    #[arg(long, default_value = DEFAULT_FROM)]
    from: String,

    /// msmtp config-fil (default: /srv/synops/config/msmtp/msmtprc)
    #[arg(long, env = "MSMTP_CONFIG")]
    config: Option<String>,

    /// Envelope-mottaker fra Postfix (--receive)
    #[arg(long)]
    recipient: Option<String>,

    /// Envelope-avsender fra Postfix (--receive)
    #[arg(long)]
    sender: Option<String>,

}

fn main() {
    let cli = Cli::parse();

    if cli.receive {
        // Motta-modus trenger async for database
        let rt = tokio::runtime::Runtime::new().unwrap_or_else(|e| {
            eprintln!("synops-mail: kunne ikke starte tokio runtime: {e}");
            std::process::exit(0);
        });
        rt.block_on(run_receive(&cli));
        return;
    }

    if !cli.send {
        eprintln!("Feil: --send eller --receive er påkrevd");
        std::process::exit(1);
    }

    run_send(&cli);
}

fn run_send(cli: &Cli) {
    let to = cli.to.as_deref().unwrap_or_else(|| {
        eprintln!("Feil: --to er påkrevd for --send");
        std::process::exit(1);
    });
    let subject = cli.subject.as_deref().unwrap_or_else(|| {
        eprintln!("Feil: --subject er påkrevd for --send");
        std::process::exit(1);
    });

    // Les body fra --body eller stdin
    let body = match &cli.body {
        Some(b) => b.clone(),
        None => {
            let mut buf = String::new();
            io::stdin().read_to_string(&mut buf).unwrap_or_else(|e| {
                eprintln!("Feil ved lesing fra stdin: {e}");
                std::process::exit(1);
            });
            if buf.is_empty() {
                eprintln!("Feil: Ingen --body og ingen data på stdin");
                std::process::exit(1);
            }
            buf
        }
    };

    let config_path = cli.config.as_deref().unwrap_or(DEFAULT_CONFIG);

    // Bygg RFC 5322-melding
    let message = format!(
        "From: {from_name} <{from}>\r\n\
         To: {to}\r\n\
         Subject: {subject}\r\n\
         MIME-Version: 1.0\r\n\
         Content-Type: text/plain; charset=utf-8\r\n\
         Content-Transfer-Encoding: 8bit\r\n\
         \r\n\
         {body}",
        from_name = DEFAULT_FROM_NAME,
        from = cli.from,
    );

    // Send via msmtp
    let mut child = Command::new("msmtp")
        .args(["--file", config_path, "--from", &cli.from, to])
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .unwrap_or_else(|e| {
            eprintln!("Kunne ikke starte msmtp: {e}");
            std::process::exit(1);
        });

    if let Some(mut stdin) = child.stdin.take() {
        use std::io::Write;
        stdin.write_all(message.as_bytes()).unwrap_or_else(|e| {
            eprintln!("Feil ved skriving til msmtp: {e}");
            std::process::exit(1);
        });
    }

    let output = child.wait_with_output().unwrap_or_else(|e| {
        eprintln!("Feil ved venting på msmtp: {e}");
        std::process::exit(1);
    });

    if output.status.success() {
        println!("Epost sendt til {}", to);
    } else {
        let stderr = String::from_utf8_lossy(&output.stderr);
        eprintln!("msmtp feilet (exit {}): {}", output.status, stderr);
        std::process::exit(output.status.code().unwrap_or(1));
    }
}

/// Motta epost fra Postfix pipe transport.
///
/// 1. Leser rå RFC 5322-epost fra stdin
/// 2. Parser eposten for å ekstrahere body og subject
/// 3. Sjekker at envelope-sender finnes i auth_identities.email
/// 4. Sjekker at body starter med aktiveringsfrasen
/// 5. Oppretter content-node med epostinnholdet
///
/// Exit 0 alltid — Postfix skal aldri prøve på nytt (ingen bounce).
async fn run_receive(cli: &Cli) {
    synops_common::logging::init("synops_mail");

    let recipient = cli.recipient.as_deref().unwrap_or("(ukjent)");
    let sender = cli.sender.as_deref().unwrap_or("(ukjent)");

    // Les rå epost fra stdin
    let mut raw_email = Vec::new();
    if let Err(e) = io::stdin().read_to_end(&mut raw_email) {
        tracing::error!("feil ved lesing fra stdin: {e}");
        return; // exit 0
    }

    tracing::info!(
        from = sender,
        to = recipient,
        size = raw_email.len(),
        "epost mottatt"
    );

    // Parse eposten
    let parsed = match mailparse::parse_mail(&raw_email) {
        Ok(p) => p,
        Err(e) => {
            tracing::warn!("kunne ikke parse epost: {e}");
            return;
        }
    };

    // Ekstraher subject fra headers
    let subject = parsed
        .headers
        .iter()
        .find(|h| h.get_key().eq_ignore_ascii_case("subject"))
        .map(|h| h.get_value())
        .unwrap_or_default();

    // Ekstraher body (plain text)
    let body = extract_plain_text(&parsed);

    tracing::info!(subject = %subject, body_len = body.len(), "epost parset");

    // Verifiser avsender: epost matcher auth_identities.email?
    let db = match synops_common::db::connect().await {
        Ok(pool) => pool,
        Err(e) => {
            tracing::error!("database-tilkobling feilet: {e}");
            return;
        }
    };

    let sender_email = sender.trim().to_lowercase();

    let user_node_id: Option<uuid::Uuid> = match sqlx::query_scalar(
        "SELECT node_id FROM auth_identities WHERE LOWER(email) = $1",
    )
    .bind(&sender_email)
    .fetch_optional(&db)
    .await
    {
        Ok(id) => id,
        Err(e) => {
            tracing::error!("database-spørring feilet: {e}");
            return;
        }
    };

    let user_node_id = match user_node_id {
        Some(id) => {
            tracing::info!(user_node_id = %id, "avsender verifisert via epost");
            id
        }
        None => {
            tracing::info!(
                sender = sender_email,
                "avsender ikke funnet i auth_identities — forkaster"
            );
            return;
        }
    };

    // Domene-alias: mottaker-username oppslås uavhengig av domene.
    // vegard@synops.no, vegard@sidelinja.org, vegard@vegard.info
    // ruter alle til samme bruker.
    let recipient_username = recipient
        .split('@')
        .next()
        .unwrap_or("")
        .trim()
        .to_lowercase();

    let target_node_id: Option<uuid::Uuid> = match sqlx::query_scalar(
        "SELECT node_id FROM auth_identities WHERE LOWER(username) = $1",
    )
    .bind(&recipient_username)
    .fetch_optional(&db)
    .await
    {
        Ok(id) => id,
        Err(e) => {
            tracing::warn!("username-oppslag feilet: {e}");
            None
        }
    };

    // Hvis mottaker-username matcher en bruker, bruk den.
    // Ellers havner noden hos avsender (epost til seg selv / vaktmester).
    let owner_node_id = target_node_id.unwrap_or(user_node_id);
    if target_node_id.is_some() {
        tracing::info!(
            recipient_username = %recipient_username,
            owner = %owner_node_id,
            "mottaker funnet via username"
        );
    }

    let body_trimmed = body.trim();

    // Avsender verifisert — opprett content-node
    let node_id = uuid::Uuid::now_v7();
    let title = if subject.is_empty() {
        format!("Epost fra {}", sender_email)
    } else {
        subject.clone()
    };

    let metadata = serde_json::json!({
        "source": "email",
        "from": sender_email,
        "to": recipient,
        "subject": subject,
    });

    match sqlx::query(
        "INSERT INTO nodes (id, node_kind, title, content, visibility, metadata, created_by)
         VALUES ($1, 'content', $2, $3, 'hidden', $4, $5)",
    )
    .bind(node_id)
    .bind(&title)
    .bind(body_trimmed)
    .bind(&metadata)
    .bind(owner_node_id)
    .execute(&db)
    .await
    {
        Ok(_) => {
            tracing::info!(
                node_id = %node_id,
                owner = %owner_node_id,
                title = %title,
                "content-node opprettet fra epost"
            );
        }
        Err(e) => {
            tracing::error!("kunne ikke opprette node: {e}");
        }
    }
}

/// Ekstraher plain text-body fra en parset epost.
/// Håndterer enkel body og multipart (tar første text/plain-del).
/// Prøver raw UTF-8 først for å unngå mailparse sin latin-1-fallback.
fn extract_plain_text(mail: &mailparse::ParsedMail) -> String {
    if mail.subparts.is_empty() {
        // Enkel (ikke-multipart) epost — prøv raw UTF-8 først
        return get_body_utf8(mail);
    }

    // Multipart — finn første text/plain
    for part in &mail.subparts {
        let ctype = part.ctype.mimetype.to_lowercase();
        if ctype == "text/plain" {
            return get_body_utf8(part);
        }
    }

    // Fallback: prøv første del
    mail.subparts
        .first()
        .map(|p| get_body_utf8(p))
        .unwrap_or_default()
}

/// Hent body fra en mail-del. Prøver raw bytes som UTF-8 først,
/// faller tilbake til mailparse sin charset-konvertering.
fn get_body_utf8(part: &mailparse::ParsedMail) -> String {
    // Hvis raw body-bytes er gyldig UTF-8, bruk det direkte
    if let Ok(raw) = part.get_body_raw() {
        if let Ok(s) = String::from_utf8(raw) {
            return s;
        }
    }
    // Ellers la mailparse håndtere charset-konvertering
    part.get_body().unwrap_or_default()
}
