use std::fs;
use std::io::{self, Write};
use std::path::Path;
use std::process::ExitCode;

use sbol::{RdfFormat, WriteError};

use crate::cli::ConvertArgs;
use crate::output::read_document;
use crate::style::Styles;

pub(crate) fn convert(args: ConvertArgs, styles: Styles) -> ExitCode {
    let writing_to_stdout = args.output == "-";
    let target_format = match args.to {
        Some(format) => RdfFormat::from(format),
        None => {
            if writing_to_stdout {
                eprintln!(
                    "{}: --to is required when writing to stdout; \
                     pass --to <FORMAT> or --output <PATH>",
                    styles.err_label()
                );
                return ExitCode::from(2);
            }
            match RdfFormat::from_path(Path::new(&args.output)) {
                Some(format) => format,
                None => {
                    eprintln!(
                        "{}: cannot infer target format from `{}` — pass --to <FORMAT> \
                         (one of: turtle, rdfxml, jsonld, ntriples)",
                        styles.err_label(),
                        args.output
                    );
                    return ExitCode::from(2);
                }
            }
        }
    };

    let document = match read_document(&args.path, styles) {
        Ok(document) => document,
        Err(code) => return code,
    };

    let payload = match document.write(target_format) {
        Ok(payload) => payload,
        Err(WriteError::Io { source, .. }) => {
            eprintln!(
                "{}: failed to serialize as {}: {source}",
                styles.err_label(),
                target_format
            );
            return ExitCode::from(2);
        }
        Err(error) => {
            eprintln!(
                "{}: failed to serialize as {}: {error}",
                styles.err_label(),
                target_format
            );
            return ExitCode::from(2);
        }
    };

    if writing_to_stdout {
        let mut stdout = io::stdout().lock();
        if let Err(error) = stdout.write_all(payload.as_bytes()) {
            eprintln!("{}: failed to write output: {error}", styles.err_label());
            return ExitCode::from(2);
        }
        if !payload.ends_with('\n') && stdout.write_all(b"\n").is_err() {
            return ExitCode::from(2);
        }
    } else if let Err(error) = fs::write(&args.output, payload) {
        eprintln!(
            "{}: failed to write {}: {error}",
            styles.err_label(),
            args.output
        );
        return ExitCode::from(2);
    }
    ExitCode::SUCCESS
}
