//! Core ECMQV shared point computation.

#![allow(non_snake_case, unreachable_patterns, unused_variables, unused_imports)]

use crate::error::AgreementError;
use crate::key::{EphemeralKey, PublicKey, StaticKey};
use crate::params::CurveParameters;
use alloc::vec::Vec;

// Conditionally import curve‑specific items and the Curve trait
#[cfg(feature = "curve25519")]
use curve25519_dalek::traits::Identity;
#[cfg(feature = "curve25519")]
use crate::params::Curve25519;
#[cfg(any(feature = "curve25519", feature = "secp256k1", feature = "secp256r1"))]
use crate::params::Curve; // required for trait method calls

#[cfg(feature = "secp256k1")]
use crate::params::Secp256k1;

#[cfg(feature = "secp256r1")]
use crate::params::Secp256r1;

/// Compute the shared ECMQV point (raw elliptic curve point) from key material.
pub fn compute_shared_point(
    own_eph: &EphemeralKey,
    own_static: Option<&StaticKey>,
    peer_eph: &PublicKey,
    peer_static: Option<&PublicKey>,
    curve: &CurveParameters,
) -> Result<Vec<u8>, AgreementError> {
    // Verify all keys belong to the same curve
    if own_eph.curve_id() != curve.curve_id() {
        return Err(AgreementError::ParameterMismatch);
    }
    if let Some(s) = own_static {
        if s.curve_id() != curve.curve_id() {
            return Err(AgreementError::ParameterMismatch);
        }
    }
    if peer_eph.curve_id() != curve.curve_id() {
        return Err(AgreementError::ParameterMismatch);
    }
    if let Some(p) = peer_static {
        if p.curve_id() != curve.curve_id() {
            return Err(AgreementError::ParameterMismatch);
        }
    }

    // Dispatch to concrete curve implementation
    match curve {
        #[cfg(feature = "curve25519")]
        CurveParameters::Curve25519 => {
            compute_curve25519(own_eph, own_static, peer_eph, peer_static)
        }
        #[cfg(feature = "secp256k1")]
        CurveParameters::Secp256k1 => {
            compute_secp256k1(own_eph, own_static, peer_eph, peer_static)
        }
        #[cfg(feature = "secp256r1")]
        CurveParameters::Secp256r1 => {
            compute_secp256r1(own_eph, own_static, peer_eph, peer_static)
        }
        _ => Err(AgreementError::UnsupportedCurve),
    }
}

#[cfg(feature = "curve25519")]
// Curve25519 implementation.
fn compute_curve25519(
    own_eph: &EphemeralKey,
    own_static: Option<&StaticKey>,
    peer_eph: &PublicKey,
    peer_static: Option<&PublicKey>,
) -> Result<Vec<u8>, AgreementError> {
    use curve25519_dalek::{edwards::EdwardsPoint, scalar::Scalar};
    use crate::params::Curve25519 as C;
    let (r, R) = match own_eph {
        EphemeralKey::Curve25519(inner) => (inner.secret(), inner.public()),
        _ => unreachable!(),
    };
    let (s, _S) = match own_static {
        Some(StaticKey::Curve25519(inner)) => (Some(inner.secret()), Some(inner.public())),
        Some(_) => unreachable!(),
        None => (None, None),
    };
    let Y = match peer_eph {
        PublicKey::Curve25519(point) => point,
        _ => unreachable!(),
    };
    let B = match peer_static {
        Some(PublicKey::Curve25519(point)) => Some(point),
        Some(_) => unreachable!(),
        None => None,
    };

    let r_bar = C::associate_value(R);
    let zero = Scalar::from(0u64);
    let s_val = s.cloned().unwrap_or(zero);
    let t = *r + r_bar * s_val;

    let y_bar = C::associate_value(Y);
    let b_point = match B {
        Some(b) => *b,
        None => EdwardsPoint::identity(),
    };
    let peer_combined = C::add_points(Y, &C::mul_scalar(&b_point, &y_bar));

    let h = Scalar::from(C::cofactor());
    let scalar = h * t;
    let K = C::mul_scalar(&peer_combined, &scalar);

    if C::is_identity(&K) {
        return Err(AgreementError::SharedPointIsIdentity);
    }

    Ok(C::public_key_to_bytes(&K))
}

#[cfg(feature = "secp256k1")]
fn compute_secp256k1(
    own_eph: &EphemeralKey,
    own_static: Option<&StaticKey>,
    peer_eph: &PublicKey,
    peer_static: Option<&PublicKey>,
) -> Result<Vec<u8>, AgreementError> {
    use k256::{AffinePoint, Scalar};
    use crate::params::Secp256k1 as C;

    let (r, R) = match own_eph {
        EphemeralKey::Secp256k1(inner) => (inner.secret(), inner.public()),
        _ => unreachable!(),
    };
    let (s, _S) = match own_static {
        Some(StaticKey::Secp256k1(inner)) => (Some(inner.secret()), Some(inner.public())),
        Some(_) => unreachable!(),
        None => (None, None),
    };
    let Y = match peer_eph {
        PublicKey::Secp256k1(point) => point,
        _ => unreachable!(),
    };
    let B = match peer_static {
        Some(PublicKey::Secp256k1(point)) => Some(point),
        Some(_) => unreachable!(),
        None => None,
    };

    let r_bar = C::associate_value(R);
    let zero = Scalar::from(0u64);
    let s_val = s.cloned().unwrap_or(zero);
    let t = *r + r_bar * s_val;

    let y_bar = C::associate_value(Y);
    let b_point = match B {
        Some(b) => *b,
        None => AffinePoint::IDENTITY,
    };
    let peer_combined = C::add_points(Y, &C::mul_scalar(&b_point, &y_bar));

    let h = Scalar::from(C::cofactor());
    let scalar = h * t;
    let K = C::mul_scalar(&peer_combined, &scalar);

    if C::is_identity(&K) {
        return Err(AgreementError::SharedPointIsIdentity);
    }

    Ok(C::public_key_to_bytes(&K))
}

#[cfg(feature = "secp256r1")]
fn compute_secp256r1(
    own_eph: &EphemeralKey,
    own_static: Option<&StaticKey>,
    peer_eph: &PublicKey,
    peer_static: Option<&PublicKey>,
) -> Result<Vec<u8>, AgreementError> {
    use p256::{AffinePoint, Scalar};
    use crate::params::Secp256r1 as C;

    let (r, R) = match own_eph {
        EphemeralKey::Secp256r1(inner) => (inner.secret(), inner.public()),
        _ => unreachable!(),
    };
    let (s, _S) = match own_static {
        Some(StaticKey::Secp256r1(inner)) => (Some(inner.secret()), Some(inner.public())),
        Some(_) => unreachable!(),
        None => (None, None),
    };
    let Y = match peer_eph {
        PublicKey::Secp256r1(point) => point,
        _ => unreachable!(),
    };
    let B = match peer_static {
        Some(PublicKey::Secp256r1(point)) => Some(point),
        Some(_) => unreachable!(),
        None => None,
    };

    let r_bar = C::associate_value(R);
    let zero = Scalar::from(0u64);
    let s_val = s.cloned().unwrap_or(zero);
    let t = *r + r_bar * s_val;

    let y_bar = C::associate_value(Y);
    let b_point = match B {
        Some(b) => *b,
        None => AffinePoint::IDENTITY,
    };
    let peer_combined = C::add_points(Y, &C::mul_scalar(&b_point, &y_bar));

    let h = Scalar::from(C::cofactor());
    let scalar = h * t;
    let K = C::mul_scalar(&peer_combined, &scalar);

    if C::is_identity(&K) {
        return Err(AgreementError::SharedPointIsIdentity);
    }

    Ok(C::public_key_to_bytes(&K))
}