use serde::{Deserialize, Serialize};
use time::OffsetDateTime;

use crate::{
    edition::{MachineAccessMode, OperationSecurityLevel},
    ids::{AgentId, AuthProfileId, OperationId, SecretId, WorkspaceId},
    protocol::AuthKind,
};

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BearerAuthConfig {
    pub header_name: String,
    pub secret_id: SecretId,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BasicAuthConfig {
    pub username_secret_id: SecretId,
    pub password_secret_id: SecretId,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ApiKeyHeaderAuthConfig {
    pub header_name: String,
    pub secret_id: SecretId,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ApiKeyQueryAuthConfig {
    pub param_name: String,
    pub secret_id: SecretId,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AuthConfig {
    Bearer(BearerAuthConfig),
    Basic(BasicAuthConfig),
    ApiKeyHeader(ApiKeyHeaderAuthConfig),
    ApiKeyQuery(ApiKeyQueryAuthConfig),
}

impl AuthConfig {
    pub fn secret_ids(&self) -> Vec<&SecretId> {
        match self {
            Self::Bearer(config) => vec![&config.secret_id],
            Self::Basic(config) => vec![&config.username_secret_id, &config.password_secret_id],
            Self::ApiKeyHeader(config) => vec![&config.secret_id],
            Self::ApiKeyQuery(config) => vec![&config.secret_id],
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuthProfile {
    pub id: AuthProfileId,
    pub workspace_id: WorkspaceId,
    pub name: String,
    pub kind: AuthKind,
    pub config: AuthConfig,
    #[serde(with = "time::serde::rfc3339")]
    pub created_at: OffsetDateTime,
    #[serde(with = "time::serde::rfc3339")]
    pub updated_at: OffsetDateTime,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AgentTokenGrantType {
    AgentKey,
    RefreshToken,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct IssueAgentTokenRequest {
    pub grant_type: AgentTokenGrantType,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub agent_key: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub refresh_token: Option<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub scope: Vec<String>,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct IssueOneTimeAgentTokenRequest {
    pub agent_key: String,
    pub operation_id: OperationId,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub scope: Vec<String>,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct IssuedAgentTokenResponse {
    pub access_token: String,
    pub token_type: String,
    pub expires_in: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub refresh_token: Option<String>,
    pub machine_access_mode: MachineAccessMode,
    pub security_level: OperationSecurityLevel,
    pub agent_id: AgentId,
}

#[cfg(test)]
mod tests {
    use serde_json::json;
    use time::{OffsetDateTime, format_description::well_known::Rfc3339};

    use super::{
        AgentTokenGrantType, ApiKeyHeaderAuthConfig, AuthConfig, AuthProfile,
        IssueAgentTokenRequest, IssueOneTimeAgentTokenRequest, IssuedAgentTokenResponse,
    };
    use crate::{
        edition::{MachineAccessMode, OperationSecurityLevel},
        ids::{AuthProfileId, SecretId, WorkspaceId},
        protocol::AuthKind,
    };

    #[test]
    fn auth_profile_serializes_timestamps_as_rfc3339() {
        let profile = AuthProfile {
            id: AuthProfileId::new("auth_01"),
            workspace_id: WorkspaceId::new("ws_default"),
            name: "header".to_owned(),
            kind: AuthKind::ApiKeyHeader,
            config: AuthConfig::ApiKeyHeader(ApiKeyHeaderAuthConfig {
                header_name: "X-Api-Key".to_owned(),
                secret_id: SecretId::new("secret_01"),
            }),
            created_at: OffsetDateTime::parse("2026-03-25T12:00:00Z", &Rfc3339).unwrap(),
            updated_at: OffsetDateTime::parse("2026-03-25T12:05:00Z", &Rfc3339).unwrap(),
        };

        let value = serde_json::to_value(&profile).unwrap();

        assert_eq!(value["created_at"], json!("2026-03-25T12:00:00Z"));
        assert_eq!(value["updated_at"], json!("2026-03-25T12:05:00Z"));
    }

    #[test]
    fn issue_agent_token_request_serializes_stable_contract() {
        let request = IssueAgentTokenRequest {
            grant_type: AgentTokenGrantType::AgentKey,
            agent_key: Some("crk_agent_secret".to_owned()),
            refresh_token: None,
            scope: vec!["tools:call".to_owned()],
        };

        let value = serde_json::to_value(&request).unwrap();

        assert_eq!(value["grant_type"], json!("agent_key"));
        assert_eq!(value["agent_key"], json!("crk_agent_secret"));
        assert_eq!(value["scope"], json!(["tools:call"]));
        assert_eq!(value.get("refresh_token"), None);
    }

    #[test]
    fn issue_one_time_agent_token_request_serializes_stable_contract() {
        let request = IssueOneTimeAgentTokenRequest {
            agent_key: "crk_agent_secret".to_owned(),
            operation_id: "op_01".into(),
            scope: vec!["tools:call".to_owned()],
        };

        let value = serde_json::to_value(&request).unwrap();

        assert_eq!(value["agent_key"], json!("crk_agent_secret"));
        assert_eq!(value["operation_id"], json!("op_01"));
        assert_eq!(value["scope"], json!(["tools:call"]));
    }

    #[test]
    fn issued_agent_token_response_serializes_machine_access_metadata() {
        let response = IssuedAgentTokenResponse {
            access_token: "tok_01".to_owned(),
            token_type: "Bearer".to_owned(),
            expires_in: 300,
            refresh_token: Some("rfr_01".to_owned()),
            machine_access_mode: MachineAccessMode::ShortLivedToken,
            security_level: OperationSecurityLevel::Elevated,
            agent_id: "agt_01".into(),
        };

        let value = serde_json::to_value(&response).unwrap();

        assert_eq!(value["access_token"], json!("tok_01"));
        assert_eq!(value["token_type"], json!("Bearer"));
        assert_eq!(value["expires_in"], json!(300));
        assert_eq!(value["refresh_token"], json!("rfr_01"));
        assert_eq!(value["machine_access_mode"], json!("short_lived_token"));
        assert_eq!(value["security_level"], json!("elevated"));
        assert_eq!(value["agent_id"], json!("agt_01"));
    }
}
