use crate::apt::SPOTIFY_REPOSITORY;
use crate::errors::*;
use std::collections::HashMap;
use std::env;

#[derive(Debug, PartialEq)]
pub enum Architecture {
    Amd64,
    I386,
    Unknown(&'static str),
}

impl Architecture {
    pub const fn current() -> Architecture {
        if cfg!(target_arch = "x86_64") {
            Architecture::Amd64
        } else if cfg!(target_arch = "x86") {
            Architecture::I386
        } else {
            Architecture::Unknown(env::consts::ARCH)
        }
    }

    pub const fn to_debian_str(&self) -> &str {
        match self {
            Architecture::Amd64 => "amd64",
            Architecture::I386 => "i386",
            Architecture::Unknown(arch) => arch,
        }
    }
}

#[derive(Debug, PartialEq, Default)]
pub struct Release {
    pub architectures: Vec<String>,
    pub sha256_sums: HashMap<String, String>,
}

pub fn parse_release_file(data: &str) -> Result<Release> {
    let mut section = None;
    let mut release = Release::default();

    for line in data.lines() {
        trace!("Release file parser got line: {:?}", line);

        if let Some(line) = line.strip_prefix(' ') {
            if section == Some("SHA256:") {
                let (hash, line) = line
                    .split_once(' ')
                    .context("Malformed sha256 line in release file")?;
                let (_, file) = line
                    .rsplit_once(' ')
                    .context("Malformed sha256 line in release file")?;

                debug!(
                    "Adding file hash from release file, {:?} => {:?}",
                    file, hash
                );
                release
                    .sha256_sums
                    .insert(file.to_string(), hash.to_string());
            }
        } else if line.ends_with(':') {
            section = Some(line);
        } else if let Some((key, value)) = line.split_once(": ") {
            if key == "Architectures" {
                let list = value.split(' ').map(String::from);
                release.architectures = list.collect();
            }
        }
    }

    Ok(release)
}

#[derive(Debug, PartialEq)]
pub struct Pkg {
    pub package: String,
    pub version: String,
    pub filename: String,
    pub sha256sum: String,
}

impl Pkg {
    pub fn download_url(&self) -> String {
        format!("{SPOTIFY_REPOSITORY}/{}", self.filename)
    }
}

#[derive(Debug, PartialEq, Default)]
pub struct NewPkg {
    package: Option<String>,
    version: Option<String>,
    filename: Option<String>,
    sha256sum: Option<String>,
}

impl NewPkg {
    pub fn set(&mut self, key: &str, value: String) {
        match key {
            "Package" => self.package = Some(value),
            "Version" => self.version = Some(value),
            "Filename" => self.filename = Some(value),
            "SHA256" => self.sha256sum = Some(value),
            _ => (),
        }
    }
}

pub fn parse_package_index(data: &str) -> Result<Vec<Pkg>> {
    let mut out = Vec::new();
    let mut pkg: Option<NewPkg> = None;

    for line in data.lines().chain([""]) {
        trace!("Package index parser got line: {:?}", line);

        if line.is_empty() {
            if let Some(pkg) = pkg.take() {
                out.push(Pkg {
                    package: pkg.package.context("Missing field: `package`")?,
                    version: pkg.version.context("Missing field: `version`")?,
                    filename: pkg.filename.context("Missing field: `filename`")?,
                    sha256sum: pkg.sha256sum.context("Missing field: `sha256sum`")?,
                });
            }
        } else {
            if line.starts_with(' ') || line.ends_with(':') {
                // not supported
                continue;
            }

            let (key, value) = line
                .split_once(": ")
                .with_context(|| anyhow!("Line does not have key-value format: {:?}", line))?;

            pkg.get_or_insert_with(NewPkg::default)
                .set(key, value.to_string());
        }
    }

    Ok(out)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_release() -> Result<()> {
        let data = r#"Origin: Spotify LTD
Label: Spotify Public Repository
Suite: testing
Codename: testing
Version: 0.4
Date: Wed, 27 Apr 2022 12:30:15 UTC
Architectures: amd64 i386
Components: non-free
Description: Spotify's repository for beta releases of the desktop client
MD5Sum:
 edf4635027ed7a5df78633d70657834e 1220 non-free/binary-amd64/Packages
 77247f419f4652944f6f7e674356eab5 665 non-free/binary-amd64/Packages.gz
 896ad45b10babce26185deb30607ef41 197 non-free/binary-amd64/Release
 fddbbdbabd9e75aa9435609890419f0b 1067 non-free/binary-i386/Packages
 8684c7481e8bbaa79771e64374fd615b 628 non-free/binary-i386/Packages.gz
 fce0f84291a9cdabd4458a2e1af891ba 196 non-free/binary-i386/Release
 d41d8cd98f00b204e9800998ecf8427e 0 non-free/source/Sources
 7029066c27ac6f5ef18d660d5741979a 20 non-free/source/Sources.gz
 ea17201e3c955b95e948af6c95a5f698 198 non-free/source/Release
SHA1:
 71f7a00f6f8f16677396a5366df3b1f288d7e8d5 1220 non-free/binary-amd64/Packages
 cf491797f25ec68e852ae85c3f3836175523a3c1 665 non-free/binary-amd64/Packages.gz
 4faadfa7ad9ad0ec07f8f17f31963878f5fe02df 197 non-free/binary-amd64/Release
 da9551bbd5defe5de3a2673d04782e69609518cc 1067 non-free/binary-i386/Packages
 76a712620b1483e31111492d4abd1cf8b76b73b9 628 non-free/binary-i386/Packages.gz
 b63d39ad880704ae8bf33b57504e25728068e49b 196 non-free/binary-i386/Release
 da39a3ee5e6b4b0d3255bfef95601890afd80709 0 non-free/source/Sources
 46c6643f07aa7f6bfe7118de926b86defc5087c4 20 non-free/source/Sources.gz
 3c5cc8f592faad9f57a4045cd6193615786ae98d 198 non-free/source/Release
SHA256:
 7eb86d0a8bbbfb356b2c641f039214ad30f7f5d7faabdf546d5f83d4f0f574cd 1220 non-free/binary-amd64/Packages
 8c55f74c379873d3b4bb63b7e05eff20705e9f08348b93b9e20d3baa8d27d383 665 non-free/binary-amd64/Packages.gz
 35876d5aa96d00b39fe3a26660a7665b4ced399a650dc3344b20dee7ad3cc766 197 non-free/binary-amd64/Release
 497184ddb1dc525de81a1bd98ac97175b176d15ead076680d3e8d27b0b5329c8 1067 non-free/binary-i386/Packages
 2654df365b0dd96a6307e6e56780815d8695ed89da547c1a6beb509035a8ddd0 628 non-free/binary-i386/Packages.gz
 1b0d97da546cdcd460e99eaadaac929048b217a9e043a2a919a648910e8f1be4 196 non-free/binary-i386/Release
 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/source/Sources
 59869db34853933b239f1e2219cf7d431da006aa919635478511fabbfc8849d2 20 non-free/source/Sources.gz
 963be5cb6b84350b820fd4bb4ce5059bb4cbb16dcfa7614c630ded76a09155a5 198 non-free/source/Release
"#;
        let parsed = parse_release_file(data)?;
        assert_eq!(
            parsed,
            Release {
                architectures: vec!["amd64".to_string(), "i386".to_string()],
                sha256_sums: [
                    (
                        "non-free/binary-amd64/Packages".into(),
                        "7eb86d0a8bbbfb356b2c641f039214ad30f7f5d7faabdf546d5f83d4f0f574cd".into(),
                    ),
                    (
                        "non-free/binary-amd64/Packages.gz".into(),
                        "8c55f74c379873d3b4bb63b7e05eff20705e9f08348b93b9e20d3baa8d27d383".into(),
                    ),
                    (
                        "non-free/binary-amd64/Release".into(),
                        "35876d5aa96d00b39fe3a26660a7665b4ced399a650dc3344b20dee7ad3cc766".into(),
                    ),
                    (
                        "non-free/binary-i386/Packages".into(),
                        "497184ddb1dc525de81a1bd98ac97175b176d15ead076680d3e8d27b0b5329c8".into(),
                    ),
                    (
                        "non-free/binary-i386/Packages.gz".into(),
                        "2654df365b0dd96a6307e6e56780815d8695ed89da547c1a6beb509035a8ddd0".into(),
                    ),
                    (
                        "non-free/binary-i386/Release".into(),
                        "1b0d97da546cdcd460e99eaadaac929048b217a9e043a2a919a648910e8f1be4".into(),
                    ),
                    (
                        "non-free/source/Sources".into(),
                        "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".into(),
                    ),
                    (
                        "non-free/source/Sources.gz".into(),
                        "59869db34853933b239f1e2219cf7d431da006aa919635478511fabbfc8849d2".into(),
                    ),
                    (
                        "non-free/source/Release".into(),
                        "963be5cb6b84350b820fd4bb4ce5059bb4cbb16dcfa7614c630ded76a09155a5".into(),
                    ),
                ]
                .into_iter()
                .collect(),
            }
        );
        Ok(())
    }

    #[test]
    fn test_parse_package_index() -> Result<()> {
        let data = r#"Package: spotify-client
Architecture: amd64
Version: 1:1.1.84.716.gc5f8b819
Priority: extra
Section: sound
Maintainer: Spotify <tux@spotify.com>
Installed-Size: 291694
Depends: libasound2, libatk-bridge2.0-0, libatomic1, libcurl3-gnutls, libgbm1, libgconf-2-4, libglib2.0-0, libgtk-3-0, libnss3, libssl3 | libssl1.1 | libssl1.0.2 | libssl1.0.1 | libssl1.0.0, libxshmfence1, libxss1, libxtst6, xdg-utils
Recommends: libavcodec58 | libavcodec-extra58 | libavcodec57 | libavcodec-extra57 | libavcodec-ffmpeg56 | libavcodec-ffmpeg-extra56 | libavcodec54 | libavcodec-extra-54, libavformat58 | libavformat57 | libavformat-ffmpeg56 | libavformat54
Suggests: libnotify4
Filename: pool/non-free/s/spotify-client/spotify-client_1.1.84.716.gc5f8b819_amd64.deb
SHA512: 3cc25f28ae791ac26607117a5df668f803ed8e58f0ace085010a6242fdde97766bdc1c752560850795c9b4324f3e019937fe9af2788a1946ebb70ee781f50d99
Homepage: https://www.spotify.com
Size: 119770140
SHA256: 08e6b2666dc2a39624890e553a3046d05ecebe17bcc2fe930d49314b2fb812c7
SHA1: 987258467c50076490400b2539688f0808b86ebb
MD5sum: 57c7e2f950b25ea26328abf4b232555a
Description: Spotify streaming music client
License: https://www.spotify.com/legal/end-user-agreement
Vendor: Spotify AB
"#;
        let parsed = parse_package_index(data)?;
        assert_eq!(
            parsed,
            &[Pkg {
                package: "spotify-client".into(),
                version: "1:1.1.84.716.gc5f8b819".into(),
                filename:
                    "pool/non-free/s/spotify-client/spotify-client_1.1.84.716.gc5f8b819_amd64.deb"
                        .into(),
                sha256sum: "08e6b2666dc2a39624890e553a3046d05ecebe17bcc2fe930d49314b2fb812c7"
                    .into(),
            },]
        );
        Ok(())
    }
}
