use std::path::PathBuf;

use martin_tile_utils::{MAX_ZOOM, TileInfo};
use sqlite_hashes::rusqlite;

use crate::{AGG_TILES_HASH, AGG_TILES_HASH_AFTER_APPLY, AGG_TILES_HASH_BEFORE_APPLY, MbtType};

#[non_exhaustive]
#[derive(thiserror::Error, Debug)]
pub enum MbtError {
    #[error("The source and destination MBTiles files are the same: {0}")]
    SameSourceAndDestination(PathBuf),

    #[error("The diff file and source MBTiles files are the same: {0}")]
    SameDiffAndSource(PathBuf),

    #[error("The diff file and destination MBTiles files are the same: {0}")]
    SameDiffAndDestination(PathBuf),

    #[error("The patch file and source MBTiles files are the same: {0}")]
    SamePatchAndSource(PathBuf),

    #[error("The patch file and destination MBTiles files are the same: {0}")]
    SamePatchAndDestination(PathBuf),

    #[error(transparent)]
    SqlxError(#[from] sqlx::Error),

    #[error(transparent)]
    RusqliteError(#[from] rusqlite::Error),

    #[error(transparent)]
    JsonSerdeError(#[from] serde_json::Error),

    #[error("MBTile filepath contains unsupported characters: {0}")]
    UnsupportedCharsInFilepath(PathBuf),

    #[error("Inconsistent tile formats detected: {0} vs {1}")]
    InconsistentMetadata(TileInfo, TileInfo),

    #[error("Invalid data format for MBTile file {0}")]
    InvalidDataFormat(String),

    #[error("Integrity check failed for MBTile file {0} for the following reasons:\n    {1:?}")]
    FailedIntegrityCheck(String, Vec<String>),

    #[error(
        "At least one tile has mismatching hash: stored value is `{1}` != computed value `{2}` in MBTile file {0}"
    )]
    IncorrectTileHash(String, String, String),

    #[error("Map table references tile id `{1}` that does not exist in `{2}` in MBTile file {0}")]
    MissingTileReference(String, String, &'static str),

    #[error(
        "At least one tile in the tiles table/view has an invalid value: zoom_level={1}, tile_column={2}, tile_row={3} in MBTile file {0}"
    )]
    InvalidTileIndex(String, String, String, String),

    #[error(
        "Computed aggregate tiles hash {0} does not match tile data in metadata {1} for MBTile file {2}"
    )]
    AggHashMismatch(String, String, String),

    #[error(
        "Metadata value `agg_tiles_hash` is not set in MBTiles file {0}\n    Use `mbtiles validate --agg-hash update {0}` to fix this."
    )]
    AggHashValueNotFound(String),

    #[error(r#"Filename "{0}" passed to SQLite must be valid UTF-8"#)]
    InvalidFilenameType(PathBuf),

    #[error("No tiles found")]
    NoTilesFound,

    #[error(
        "The destination file {0} is not empty. Some operations like creating a diff file require the destination file to be non-existent or empty."
    )]
    NonEmptyTargetFile(PathBuf),

    #[error("The file {0} does not have the required uniqueness constraint")]
    NoUniquenessConstraint(String),

    #[error("Could not copy MBTiles file: {reason}")]
    UnsupportedCopyOperation { reason: String },

    #[error("Unexpected duplicate tiles found when copying")]
    DuplicateValues,

    #[error("Applying a patch while diffing is not supported")]
    CannotApplyPatchAndDiff,

    #[error("The MBTiles file {0} has data of type {1}, but the desired type was set to {2}")]
    MismatchedTargetType(PathBuf, MbtType, MbtType),

    #[error(
        "Unless  --on-duplicate (override|ignore|abort)  is set, writing tiles to an existing non-empty MBTiles file is disabled. Either set --on-duplicate flag, or delete {0}"
    )]
    DestinationFileExists(PathBuf),

    #[error("Invalid zoom value {0}={1}, expecting an integer between 0..{MAX_ZOOM}")]
    InvalidZoomValue(&'static str, String),

    #[error(
        "A file {0} does not have an {AGG_TILES_HASH} metadata entry, probably because it was not created by this tool. Use `--force` to ignore this warning, or run this to update hash value: `mbtiles validate --agg-hash update {0}`"
    )]
    CannotDiffFileWithoutHash(String),

    #[error(
        "File {0} has {AGG_TILES_HASH_BEFORE_APPLY} or {AGG_TILES_HASH_AFTER_APPLY} metadata entry, indicating it is a patch file which should not be diffed with another file.  Use `--force` to ignore this warning."
    )]
    DiffingDiffFile(String),

    #[error(
        "A file {0} does not seem to be a patch diff file because it has no {AGG_TILES_HASH_BEFORE_APPLY} and {AGG_TILES_HASH_AFTER_APPLY} metadata entries.  These entries are automatically created when using `mbtiles diff` and `mbitiles copy --diff-with-file`.  Use `--force` to ignore this warning."
    )]
    PatchFileHasNoHashes(String),

    #[error(
        "A file {0} does not have {AGG_TILES_HASH_BEFORE_APPLY} metadata, probably because it was created by an older version of the `mbtiles` tool.  Use `--force` to ignore this warning, but ensure you are applying the patch to the right file."
    )]
    PatchFileHasNoBeforeHash(String),

    #[error(
        "The {AGG_TILES_HASH_BEFORE_APPLY}='{1}' in patch file {0} does not match {AGG_TILES_HASH}='{3}' in the file {2}"
    )]
    AggHashMismatchWithDiff(String, String, String, String),

    #[error(
        "The {AGG_TILES_HASH_AFTER_APPLY}='{1}' in patch file {0} does not match {AGG_TILES_HASH}='{3}' in the file {2} after the patch was applied"
    )]
    AggHashMismatchAfterApply(String, String, String, String),

    #[error(
        "MBTile of type {0} is not supported when using bin-diff.  The bin-diff format only works with flat and flat-with-hash MBTiles files."
    )]
    BinDiffRequiresFlatWithHash(MbtType),

    #[error(
        "Applying bindiff to tile {0} resulted in mismatching hash: expecting `{1}` != computed uncompressed value `{2}`"
    )]
    BinDiffIncorrectTileHash(String, String, String),

    #[error("Unable to generate or apply bin-diff patch")]
    BindiffError,

    #[error("BinDiff patch files can be only applied with `mbtiles copy --apply-patch` command")]
    UnsupportedPatchType,

    #[error(transparent)]
    IoError(#[from] std::io::Error),

    #[cfg(feature = "transcode")]
    #[error("Transcoding error: {0}")]
    TranscodeError(String),
}

pub type MbtResult<T> = Result<T, MbtError>;
