use actix_web::{HttpResponseBuilder, ResponseError};
use datastore::{self, deserialization, serialization};
use nix::unistd::Gid;
use snafu::Snafu;
use std::io;
use std::path::PathBuf;
use std::string::String;

// We want server (router/handler) and controller errors together so it's easy to define response
// error codes for all the high-level types of errors that could happen during a request.
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(super)))]
pub enum Error {
    // Systemd Notification errors
    #[snafu(display("Systemd notify error: {}", source))]
    SystemdNotify { source: std::io::Error },

    #[snafu(display("Failed to send systemd status notification"))]
    SystemdNotifyStatus,

    // =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=

    // Set file metadata errors
    #[snafu(display(
        "Failed to set file permissions on the API socket to {:o}: {}",
        mode,
        source
    ))]
    SetPermissions { source: std::io::Error, mode: u32 },

    #[snafu(display("Failed to set group owner on the API socket to {}: {}", gid, source))]
    SetGroup { source: nix::Error, gid: Gid },

    // =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=

    // Server errors
    #[snafu(display("Missing required input '{}'", input))]
    MissingInput { input: String },

    #[snafu(display("Input '{}' cannot be empty", input))]
    EmptyInput { input: String },

    #[snafu(display("Another thread poisoned the data store lock by panicking"))]
    DataStoreLock,

    #[snafu(display("Unable to serialize response: {}", source))]
    ResponseSerialization { source: serde_json::Error },

    #[snafu(display("Unable to bind to {}: {}", path.display(), source))]
    BindSocket { path: PathBuf, source: io::Error },

    #[snafu(display("Unable to start server: {}", source))]
    ServerStart { source: io::Error },

    #[snafu(display("Tried to commit with no pending changes"))]
    CommitWithNoPending,

    #[snafu(display("Unable to get OS release data: {}", source))]
    ReleaseData { source: bottlerocket_release::Error },

    // =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=

    // Controller errors
    #[snafu(display("Found no '{}' in datastore", prefix))]
    MissingData { prefix: String },

    #[snafu(display("Found no '{}' in datastore", requested))]
    ListKeys { requested: String },

    #[snafu(display("Listed key '{}' not found on disk", key))]
    ListedKeyNotPresent { key: String },

    #[snafu(display("Data store error during {}: {}", op, source))]
    DataStore {
        op: String,
        #[snafu(source(from(datastore::Error, Box::new)))]
        source: Box<datastore::Error>,
    },

    #[snafu(display("Error deserializing {}: {} ", given, source))]
    Deserialization {
        given: String,
        source: deserialization::Error,
    },

    #[snafu(display("Error serializing {}: {} ", given, source))]
    DataStoreSerialization {
        given: String,
        source: serialization::Error,
    },

    #[snafu(display("Error serializing {}: {} ", given, source))]
    CommandSerialization {
        given: String,
        source: serde_json::Error,
    },

    #[snafu(display("Unable to make {} key '{}': {}", key_type, name, source))]
    NewKey {
        key_type: String,
        name: String,
        #[snafu(source(from(datastore::Error, Box::new)))]
        source: Box<datastore::Error>,
    },

    #[snafu(display("Metadata '{}' is not valid JSON: {}", key, source))]
    InvalidMetadata {
        key: String,
        source: serde_json::Error,
    },

    #[snafu(display("Config applier was unable to fork child, returned {}", code))]
    ConfigApplierFork { code: String },

    #[snafu(display("Unable to start config applier: {} ", source))]
    ConfigApplierStart { source: io::Error },

    #[snafu(display("Unable to use config applier, couldn't get stdin"))]
    ConfigApplierStdin {},

    #[snafu(display(
        "Waiting on config applier failed; something else may have awaited it: {} ",
        source
    ))]
    ConfigApplierWait { source: io::Error },

    #[snafu(display("Unable to send input to config applier: {}", source))]
    ConfigApplierWrite { source: io::Error },

    #[snafu(display("Unable to start shutdown: {}", source))]
    Shutdown { source: io::Error },

    #[snafu(display("Failed to reboot, exit code: {}, stderr: {}", exit_code, stderr))]
    Reboot { exit_code: i32, stderr: String },

    #[snafu(display("Unable to generate report: {}", source))]
    ReportExec { source: io::Error },

    #[snafu(display(
        "Failed to generate report, exit code: {}, stderr: {}",
        exit_code,
        stderr
    ))]
    ReportResult { exit_code: i32, stderr: String },

    #[snafu(display("Report type must be specified"))]
    ReportTypeMissing {},

    #[snafu(display("Report type '{}' is not supported", report_type))]
    ReportNotSupported { report_type: String },

    // =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=

    // Update related errors
    #[snafu(display("Unable to start the update dispatcher: {} ", source))]
    UpdateDispatcher { source: io::Error },

    #[snafu(display("Unable to open update lock file: {}", source))]
    UpdateLockOpen { source: io::Error },

    #[snafu(display("Update lock held"))]
    UpdateLockHeld,

    #[snafu(display("Unable to obtain shared lock for reading update status: {}", source))]
    UpdateShareLock { source: io::Error },

    #[snafu(display("Previously chosen Update no longer exists"))]
    UpdateDoesNotExist,

    #[snafu(display("No update image applied to staging partition"))]
    NoStagedImage,

    #[snafu(display("Update action not allowed according to update state"))]
    DisallowCommand,

    #[snafu(display("Update dispatcher failed"))]
    UpdateError,

    #[snafu(display("Update status is uninitialized, refresh-updates to initialize it"))]
    UninitializedUpdateStatus,

    #[snafu(display("Failed to parse update status: {} ", source))]
    UpdateStatusParse { source: serde_json::Error },

    #[snafu(display(
        "Failed to parse update information from '{}': {} ",
        String::from_utf8_lossy(stdout),
        source
    ))]
    UpdateInfoParse {
        stdout: Vec<u8>,
        source: serde_json::Error,
    },
}

pub type Result<T> = std::result::Result<T, Error>;

impl From<Error> for actix_web::HttpResponse {
    fn from(e: Error) -> Self {
        // Include the error message in the response.  The Bottlerocket API is only
        // exposed locally, and only on the host filesystem and to authorized containers,
        // so we're not worried about exposing error details.
        HttpResponseBuilder::new(e.status_code()).body(format!("{}", e))
    }
}