use base64::Engine;
use url::Url;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TalParsed {
    pub raw: Vec<u8>,
    /// Lines split by '\n' and normalized by stripping a trailing '\r' per line.
    pub lines: Vec<String>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Tal {
    pub raw: Vec<u8>,
    pub comments: Vec<String>,
    pub ta_uris: Vec<Url>,
    pub subject_public_key_info_der: Vec<u8>,
}

#[derive(Debug, thiserror::Error)]
pub enum TalParseError {
    #[error("TAL must be valid UTF-8 (RFC 8630 §2.2)")]
    InvalidUtf8,
}

#[derive(Debug, thiserror::Error)]
pub enum TalProfileError {
    #[error("TAL comments must appear only at the beginning (RFC 8630 §2.2)")]
    CommentAfterHeader,

    #[error("TAL must contain at least one TA URI line (RFC 8630 §2.2)")]
    MissingTaUris,

    #[error(
        "TAL must contain an empty line separator between URI list and SPKI base64 (RFC 8630 §2.2)"
    )]
    MissingSeparatorEmptyLine,

    #[error("TAL TA URI invalid: {0} (RFC 8630 §2.2)")]
    InvalidUri(String),

    #[error("TAL TA URI scheme must be rsync or https, got {0} (RFC 8630 §2.2)")]
    UnsupportedUriScheme(String),

    #[error(
        "TAL TA URI must reference a single object (must not end with '/'): {0} (RFC 8630 §2.3)"
    )]
    UriIsDirectory(String),

    #[error(
        "TAL must contain base64-encoded SubjectPublicKeyInfo after the separator (RFC 8630 §2.2)"
    )]
    MissingSpki,

    #[error("TAL SPKI base64 decode failed (RFC 8630 §2.2)")]
    SpkiBase64Decode,

    #[error("TAL SPKI DER is empty (RFC 8630 §2.2)")]
    SpkiDerEmpty,
}

#[derive(Debug, thiserror::Error)]
pub enum TalDecodeError {
    #[error("{0}")]
    Parse(#[from] TalParseError),

    #[error("{0}")]
    Validate(#[from] TalProfileError),
}

impl Tal {
    /// Parse step of scheme A (`parse → validate → verify`).
    pub fn parse_bytes(input: &[u8]) -> Result<TalParsed, TalParseError> {
        let raw = input.to_vec();
        let text = std::str::from_utf8(input).map_err(|_| TalParseError::InvalidUtf8)?;

        let lines: Vec<String> = text
            .split('\n')
            .map(|l| l.strip_suffix('\r').unwrap_or(l).to_string())
            .collect();

        Ok(TalParsed { raw, lines })
    }

    /// Validate step of scheme A (`parse → validate → verify`).
    ///
    /// `Tal` is already profile-validated when constructed via `decode_bytes()` /
    /// `TalParsed::validate_profile()`.
    pub fn validate_profile(&self) -> Result<(), TalProfileError> {
        Ok(())
    }

    pub fn decode_bytes(input: &[u8]) -> Result<Self, TalDecodeError> {
        Ok(Self::parse_bytes(input)?.validate_profile()?)
    }
}

impl TalParsed {
    pub fn validate_profile(self) -> Result<Tal, TalProfileError> {
        let mut idx = 0usize;

        // 1) Leading comments.
        let mut comments: Vec<String> = Vec::new();
        while idx < self.lines.len() && self.lines[idx].starts_with('#') {
            comments.push(self.lines[idx][1..].to_string());
            idx += 1;
        }

        // 2) URI list (one or more non-empty lines).
        let mut ta_uris: Vec<Url> = Vec::new();
        while idx < self.lines.len() {
            let line = self.lines[idx].trim();
            if line.is_empty() {
                break;
            }
            if line.starts_with('#') {
                return Err(TalProfileError::CommentAfterHeader);
            }
            let url = match Url::parse(line) {
                Ok(u) => u,
                Err(_) => {
                    if !ta_uris.is_empty() {
                        return Err(TalProfileError::MissingSeparatorEmptyLine);
                    }
                    return Err(TalProfileError::InvalidUri(line.to_string()));
                }
            };
            match url.scheme() {
                "rsync" | "https" => {}
                s => return Err(TalProfileError::UnsupportedUriScheme(s.to_string())),
            }
            if url.path().ends_with('/') {
                return Err(TalProfileError::UriIsDirectory(line.to_string()));
            }
            if url
                .path_segments()
                .and_then(|mut s| s.next_back())
                .unwrap_or("")
                .is_empty()
            {
                return Err(TalProfileError::UriIsDirectory(line.to_string()));
            }
            ta_uris.push(url);
            idx += 1;
        }

        if ta_uris.is_empty() {
            return Err(TalProfileError::MissingTaUris);
        }

        // 3) Empty line separator (must exist).
        if idx >= self.lines.len() || !self.lines[idx].trim().is_empty() {
            return Err(TalProfileError::MissingSeparatorEmptyLine);
        }
        idx += 1;

        // 4) Base64(SPKI DER) remainder; allow line wrapping.
        let mut b64 = String::new();
        while idx < self.lines.len() {
            let line = self.lines[idx].trim();
            if !line.is_empty() {
                b64.push_str(line);
            }
            idx += 1;
        }
        if b64.is_empty() {
            return Err(TalProfileError::MissingSpki);
        }

        let spki_der = base64::engine::general_purpose::STANDARD
            .decode(b64.as_bytes())
            .map_err(|_| TalProfileError::SpkiBase64Decode)?;
        if spki_der.is_empty() {
            return Err(TalProfileError::SpkiDerEmpty);
        }

        Ok(Tal {
            raw: self.raw,
            comments,
            ta_uris,
            subject_public_key_info_der: spki_der,
        })
    }
}
