use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
use p256::ecdsa::{VerifyingKey, Signature, signature::Verifier};
use sha2::{Sha256, Digest};
use serde::{Serialize, Deserialize};

#[derive(Debug, Serialize)]
pub struct RegistrationChallenge {
    pub challenge: String,
    pub rp: RpInfo,
    pub user: UserInfo,
    pub timeout: u64,
    #[serde(rename = "pubKeyCredParams")]
    pub pub_key_cred_params: Vec<PubKeyCredParam>,
    #[serde(rename = "authenticatorSelection")]
    pub authenticator_selection: AuthenticatorSelection,
    pub attestation: String,
}

#[derive(Debug, Serialize)]
pub struct AuthenticationChallenge {
    pub challenge: String,
    pub rp_id: String,
    pub timeout: u64,
    #[serde(rename = "allowCredentials")]
    pub allow_credentials: Vec<AllowCredential>,
}

#[derive(Debug, Serialize)]
pub struct RpInfo {
    pub id: String,
    pub name: String,
}

#[derive(Debug, Serialize)]
pub struct UserInfo {
    pub id: String,
    pub name: String,
    #[serde(rename = "displayName")]
    pub display_name: String,
}

#[derive(Debug, Serialize)]
pub struct PubKeyCredParam {
    #[serde(rename = "type")]
    pub cred_type: String,
    pub alg: i64,
}

#[derive(Debug, Serialize)]
pub struct AuthenticatorSelection {
    #[serde(rename = "authenticatorAttachment")]
    pub authenticator_attachment: Option<String>,
    #[serde(rename = "residentKey")]
    pub resident_key: String,
    #[serde(rename = "requireResidentKey")]
    pub require_resident_key: bool,
    #[serde(rename = "userVerification")]
    pub user_verification: String,
}

#[derive(Debug, Serialize)]
pub struct AllowCredential {
    #[serde(rename = "type")]
    pub cred_type: String,
    pub id: String,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct StoredCredential {
    pub credential_id: Vec<u8>,
    pub public_key_x: Vec<u8>,
    pub public_key_y: Vec<u8>,
    pub counter: u32,
}

#[derive(Debug, Deserialize)]
pub struct RegistrationResponse {
    #[allow(dead_code)]
    pub id: String,
    #[serde(rename = "clientDataJSON")]
    pub client_data_json: String,
    #[serde(rename = "attestationObject")]
    pub attestation_object: String,
}

#[derive(Debug, Deserialize)]
pub struct AuthenticationResponse {
    pub id: String,
    #[serde(rename = "clientDataJSON")]
    pub client_data_json: String,
    #[serde(rename = "authenticatorData")]
    pub authenticator_data: String,
    pub signature: String,
}

#[derive(Debug, Deserialize)]
struct ClientData {
    #[serde(rename = "type")]
    ctype: String,
    challenge: String,
    origin: String,
}

pub fn generate_challenge() -> String {
    use rand::Rng;
    let mut rng = rand::rng();
    let bytes: Vec<u8> = (0..32).map(|_| rng.random::<u8>()).collect();
    URL_SAFE_NO_PAD.encode(&bytes)
}

pub fn create_registration_challenge(
    challenge: &str,
    rp_id: &str,
    rp_name: &str,
    user_id: &str,
    user_name: &str,
) -> RegistrationChallenge {
    RegistrationChallenge {
        challenge: challenge.to_string(),
        rp: RpInfo { id: rp_id.to_string(), name: rp_name.to_string() },
        user: UserInfo {
            id: URL_SAFE_NO_PAD.encode(user_id.as_bytes()),
            name: user_name.to_string(),
            display_name: user_name.to_string(),
        },
        timeout: 60000,
        pub_key_cred_params: vec![
            PubKeyCredParam { cred_type: "public-key".into(), alg: -7 }, // ES256
        ],
        authenticator_selection: AuthenticatorSelection {
            authenticator_attachment: None,
            resident_key: "preferred".into(),
            require_resident_key: false,
            user_verification: "preferred".into(),
        },
        attestation: "none".into(),
    }
}

pub fn create_authentication_challenge(
    challenge: &str,
    rp_id: &str,
    credentials: &[StoredCredential],
) -> AuthenticationChallenge {
    AuthenticationChallenge {
        challenge: challenge.to_string(),
        rp_id: rp_id.to_string(),
        timeout: 60000,
        allow_credentials: credentials.iter().map(|c| AllowCredential {
            cred_type: "public-key".into(),
            id: URL_SAFE_NO_PAD.encode(&c.credential_id),
        }).collect(),
    }
}

pub fn verify_registration(
    response: &RegistrationResponse,
    expected_challenge: &str,
    expected_origin: &str,
    expected_rp_id: &str,
) -> Result<StoredCredential, String> {
    // decode and verify clientDataJSON
    let client_data_bytes = URL_SAFE_NO_PAD.decode(&response.client_data_json)
        .map_err(|_| "invalid clientDataJSON base64")?;
    let client_data: ClientData = serde_json::from_slice(&client_data_bytes)
        .map_err(|_| "invalid clientDataJSON")?;

    if client_data.ctype != "webauthn.create" {
        return Err("wrong type in clientDataJSON".into());
    }
    if client_data.challenge != expected_challenge {
        return Err("challenge mismatch".into());
    }
    if client_data.origin != expected_origin {
        return Err(format!("origin mismatch: got {}, expected {}", client_data.origin, expected_origin));
    }

    // decode attestationObject (CBOR)
    let att_bytes = URL_SAFE_NO_PAD.decode(&response.attestation_object)
        .map_err(|_| "invalid attestationObject base64")?;
    let att: ciborium::Value = ciborium::from_reader(&att_bytes[..])
        .map_err(|e| format!("invalid CBOR: {}", e))?;

    let att_map = att.as_map().ok_or("attestationObject is not a map")?;
    let auth_data_val = att_map.iter()
        .find(|(k, _)| k.as_text() == Some("authData"))
        .map(|(_, v)| v)
        .ok_or("missing authData")?;
    let auth_data = auth_data_val.as_bytes().ok_or("authData is not bytes")?;

    if auth_data.len() < 37 {
        return Err("authData too short".into());
    }

    // verify rpIdHash
    let rp_id_hash = &auth_data[..32];
    let expected_hash = Sha256::digest(expected_rp_id.as_bytes());
    if rp_id_hash != &expected_hash[..] {
        return Err("rpIdHash mismatch".into());
    }

    // check flags: bit 0 = UP (user present), bit 6 = AT (attested credential data)
    let flags = auth_data[32];
    if flags & 0x01 == 0 {
        return Err("user not present".into());
    }
    if flags & 0x40 == 0 {
        return Err("no attested credential data".into());
    }

    let counter = u32::from_be_bytes([auth_data[33], auth_data[34], auth_data[35], auth_data[36]]);

    // parse attested credential data (starts at byte 37)
    // aaguid (16 bytes) + credentialIdLength (2 bytes) + credentialId + COSE key
    if auth_data.len() < 55 {
        return Err("authData too short for credential data".into());
    }
    let cred_id_len = u16::from_be_bytes([auth_data[53], auth_data[54]]) as usize;
    let cred_id_end = 55 + cred_id_len;
    if auth_data.len() < cred_id_end {
        return Err("authData too short for credential ID".into());
    }
    let credential_id = auth_data[55..cred_id_end].to_vec();

    // parse COSE key (remaining bytes)
    let cose_key_bytes = &auth_data[cred_id_end..];
    let cose_key: ciborium::Value = ciborium::from_reader(cose_key_bytes)
        .map_err(|e| format!("invalid COSE key CBOR: {}", e))?;

    let key_map = cose_key.as_map().ok_or("COSE key is not a map")?;
    let get_int = |label: i64| -> Option<&ciborium::Value> {
        key_map.iter().find(|(k, _)| {
            k.as_integer().and_then(|i| i64::try_from(i).ok()) == Some(label)
        }).map(|(_, v)| v)
    };

    // verify alg is ES256 (-7)
    let alg = get_int(3)
        .and_then(|v| v.as_integer())
        .and_then(|i| i64::try_from(i).ok())
        .ok_or("missing alg")?;
    if alg != -7 {
        return Err(format!("unsupported algorithm: {}", alg));
    }

    // extract x (-2) and y (-3) coordinates
    let x = get_int(-2).and_then(|v| v.as_bytes()).ok_or("missing x coordinate")?;
    let y = get_int(-3).and_then(|v| v.as_bytes()).ok_or("missing y coordinate")?;

    if x.len() != 32 || y.len() != 32 {
        return Err("invalid key coordinates".into());
    }

    Ok(StoredCredential {
        credential_id,
        public_key_x: x.to_vec(),
        public_key_y: y.to_vec(),
        counter,
    })
}

pub fn verify_authentication(
    response: &AuthenticationResponse,
    credential: &StoredCredential,
    expected_challenge: &str,
    expected_origin: &str,
    expected_rp_id: &str,
) -> Result<u32, String> {
    // decode and verify clientDataJSON
    let client_data_bytes = URL_SAFE_NO_PAD.decode(&response.client_data_json)
        .map_err(|_| "invalid clientDataJSON base64")?;
    let client_data: ClientData = serde_json::from_slice(&client_data_bytes)
        .map_err(|_| "invalid clientDataJSON")?;

    if client_data.ctype != "webauthn.get" {
        return Err("wrong type".into());
    }
    if client_data.challenge != expected_challenge {
        return Err("challenge mismatch".into());
    }
    if client_data.origin != expected_origin {
        return Err("origin mismatch".into());
    }

    // decode authenticatorData
    let auth_data = URL_SAFE_NO_PAD.decode(&response.authenticator_data)
        .map_err(|_| "invalid authenticatorData base64")?;

    if auth_data.len() < 37 {
        return Err("authenticatorData too short".into());
    }

    // verify rpIdHash
    let rp_id_hash = &auth_data[..32];
    let expected_hash = Sha256::digest(expected_rp_id.as_bytes());
    if rp_id_hash != &expected_hash[..] {
        return Err("rpIdHash mismatch".into());
    }

    // check user present flag
    if auth_data[32] & 0x01 == 0 {
        return Err("user not present".into());
    }

    let counter = u32::from_be_bytes([auth_data[33], auth_data[34], auth_data[35], auth_data[36]]);

    // verify signature: sign(authenticatorData || SHA-256(clientDataJSON))
    let client_data_hash = Sha256::digest(&client_data_bytes);
    let mut signed_data = auth_data.to_vec();
    signed_data.extend_from_slice(&client_data_hash);

    // reconstruct P-256 public key
    let mut uncompressed = vec![0x04u8];
    uncompressed.extend_from_slice(&credential.public_key_x);
    uncompressed.extend_from_slice(&credential.public_key_y);

    let verifying_key = VerifyingKey::from_sec1_bytes(&uncompressed)
        .map_err(|e| format!("invalid public key: {}", e))?;

    let sig_bytes = URL_SAFE_NO_PAD.decode(&response.signature)
        .map_err(|_| "invalid signature base64")?;

    // WebAuthn signatures are DER-encoded
    let signature = Signature::from_der(&sig_bytes)
        .map_err(|e| format!("invalid signature DER: {}", e))?;

    verifying_key.verify(&signed_data, &signature)
        .map_err(|e| format!("signature verification failed: {}", e))?;

    Ok(counter)
}
