use gaze_types::{OpenAiPrivateLabel, SafetyNetError, SafetyNetPiiClass};

const MAX_OPENAI_LABEL_LEN: usize = 32;

/// Validates OPF label shape before any class mapping.
///
/// The shape is intentionally narrower than `PiiClass::custom`: OPF labels are
/// an upstream wire contract, not adopter-defined policy names.
pub fn validate_openai_label(label: &str) -> Result<(), SafetyNetError> {
    if label.is_empty() || label.len() > MAX_OPENAI_LABEL_LEN {
        return Err(invalid_label());
    }

    if label
        .bytes()
        .all(|byte| byte.is_ascii_lowercase() || byte == b'_')
    {
        Ok(())
    } else {
        Err(invalid_label())
    }
}

/// Maps the official eight OPF labels into the closed Gaze safety-net class set.
pub fn map_openai_label(label: &str) -> Result<OpenAiPrivateLabel, SafetyNetError> {
    validate_openai_label(label)?;

    match label {
        "private_person" => Ok(OpenAiPrivateLabel::PrivatePerson),
        "private_address" => Ok(OpenAiPrivateLabel::PrivateAddress),
        "private_email" => Ok(OpenAiPrivateLabel::PrivateEmail),
        "private_phone" => Ok(OpenAiPrivateLabel::PrivatePhone),
        "private_url" => Ok(OpenAiPrivateLabel::PrivateUrl),
        "private_date" => Ok(OpenAiPrivateLabel::PrivateDate),
        "account_number" => Ok(OpenAiPrivateLabel::AccountNumber),
        "secret" => Ok(OpenAiPrivateLabel::Secret),
        _ => Err(SafetyNetError::InvalidOutput {
            message: "opf returned unsupported label".to_string(),
        }),
    }
}

/// Maps an official OPF label into the closed safety-net PII vocabulary.
pub fn openai_label_to_safety_net_class(label: OpenAiPrivateLabel) -> SafetyNetPiiClass {
    match label {
        OpenAiPrivateLabel::PrivatePerson => SafetyNetPiiClass::Name,
        OpenAiPrivateLabel::PrivateAddress => SafetyNetPiiClass::Location,
        OpenAiPrivateLabel::PrivateEmail => SafetyNetPiiClass::Email,
        OpenAiPrivateLabel::PrivatePhone => SafetyNetPiiClass::Phone,
        OpenAiPrivateLabel::PrivateUrl => SafetyNetPiiClass::Url,
        OpenAiPrivateLabel::PrivateDate => SafetyNetPiiClass::Date,
        OpenAiPrivateLabel::AccountNumber => SafetyNetPiiClass::AccountNumber,
        OpenAiPrivateLabel::Secret => SafetyNetPiiClass::Secret,
        _ => panic!("unknown OpenAI private label - update class_map.rs"),
    }
}

fn invalid_label() -> SafetyNetError {
    SafetyNetError::InvalidOutput {
        message: "opf returned invalid label".to_string(),
    }
}

#[cfg(test)]
mod tests {
    use gaze_types::PiiClass;

    use super::*;

    #[test]
    fn official_labels_map_to_closed_classes() {
        let cases = [
            ("private_person", PiiClass::Name),
            ("private_address", PiiClass::Location),
            ("private_email", PiiClass::Email),
            ("private_phone", PiiClass::custom("phone")),
            ("private_url", PiiClass::custom("url")),
            ("private_date", PiiClass::custom("date")),
            ("account_number", PiiClass::custom("account_number")),
            ("secret", PiiClass::custom("secret")),
        ];

        for (label, expected) in cases {
            let private_label = map_openai_label(label).expect("official label");
            assert_eq!(private_label.as_str(), label);
            assert_eq!(
                openai_label_to_safety_net_class(private_label).to_pii_class(),
                expected
            );
        }
    }

    #[test]
    fn unknown_and_invalid_labels_fail_closed() {
        assert!(map_openai_label("organization").is_err());
        assert!(map_openai_label("private-email").is_err());
        assert!(map_openai_label("PRIVATE_EMAIL").is_err());
        assert!(map_openai_label("private_email_123").is_err());
        assert!(map_openai_label("").is_err());
        assert!(map_openai_label("abcdefghijklmnopqrstuvwxyzabcdefg").is_err());
    }
}
