use diesel::prelude::*;
use serde_json::Value;
use webauthn_rs::prelude::{Credential, ParsedAttestation};
use webauthn_rs_core::proto::CredentialV3;
use webauthn_rs_proto::{AttestationFormat, RegisteredExtensions};

use crate::{
    api::{EmptyResult, core::two_factor::webauthn::WebauthnRegistration},
    db::{DbConn, schema::twofactor},
    error::MapResult,
};

use super::UserId;

#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[diesel(table_name = twofactor)]
#[diesel(primary_key(uuid))]
pub struct TwoFactor {
    pub uuid: TwoFactorId,
    pub user_uuid: UserId,
    pub atype: i32,
    pub enabled: bool,
    pub data: String,
    pub last_used: i64,
}

#[derive(num_derive::FromPrimitive)]
pub enum TwoFactorType {
    Authenticator = 0,
    Email = 1,
    Duo = 2,
    YubiKey = 3,
    U2f = 4,
    Remember = 5,
    OrganizationDuo = 6,
    Webauthn = 7,
    RecoveryCode = 8,

    // These are implementation details
    U2fRegisterChallenge = 1000,
    U2fLoginChallenge = 1001,
    EmailVerificationChallenge = 1002,
    WebauthnRegisterChallenge = 1003,
    WebauthnLoginChallenge = 1004,

    // Special type for Protected Actions verification via email
    ProtectedActions = 2000,
}

/// Local methods
impl TwoFactor {
    pub fn new(user_uuid: UserId, atype: TwoFactorType, data: String) -> Self {
        Self {
            uuid: TwoFactorId(crate::util::get_uuid()),
            user_uuid,
            atype: atype as i32,
            enabled: true,
            data,
            last_used: 0,
        }
    }

    pub fn to_json(&self) -> Value {
        json!({
            "enabled": self.enabled,
            "key": "", // This key and value vary
            "Oobject": "twoFactorAuthenticator" // This value varies
        })
    }

    pub fn to_json_provider(&self) -> Value {
        json!({
            "enabled": self.enabled,
            "type": self.atype,
            "object": "twoFactorProvider"
        })
    }
}

/// Database methods
impl TwoFactor {
    pub async fn save(&self, conn: &DbConn) -> EmptyResult {
        db_run! { conn:
            sqlite, mysql {
                match diesel::replace_into(twofactor::table)
                    .values(self)
                    .execute(conn)
                {
                    Ok(_) => Ok(()),
                    // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first.
                    Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
                        diesel::update(twofactor::table)
                            .filter(twofactor::uuid.eq(&self.uuid))
                            .set(self)
                            .execute(conn)
                            .map_res("Error saving twofactor")
                    }
                    Err(e) => Err(e.into()),
                }.map_res("Error saving twofactor")
            }
            postgresql {
                // We need to make sure we're not going to violate the unique constraint on user_uuid and atype.
                // This happens automatically on other DBMS backends due to replace_into(). PostgreSQL does
                // not support multiple constraints on ON CONFLICT clauses.
                let _: () = diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(&self.user_uuid)).filter(twofactor::atype.eq(&self.atype)))
                    .execute(conn)
                    .map_res("Error deleting twofactor for insert")?;

                diesel::insert_into(twofactor::table)
                    .values(self)
                    .on_conflict(twofactor::uuid)
                    .do_update()
                    .set(self)
                    .execute(conn)
                    .map_res("Error saving twofactor")
            }
        }
    }

    pub async fn delete(self, conn: &DbConn) -> EmptyResult {
        conn.run(move |conn| {
            diesel::delete(twofactor::table.filter(twofactor::uuid.eq(self.uuid)))
                .execute(conn)
                .map_res("Error deleting twofactor")
        })
        .await
    }

    pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
        conn.run(move |conn| {
            twofactor::table
                .filter(twofactor::user_uuid.eq(user_uuid))
                .filter(twofactor::atype.lt(1000)) // Filter implementation types
                .load::<Self>(conn)
                .expect("Error loading twofactor")
        })
        .await
    }

    pub async fn find_by_user_and_type(user_uuid: &UserId, atype: i32, conn: &DbConn) -> Option<Self> {
        conn.run(move |conn| {
            twofactor::table
                .filter(twofactor::user_uuid.eq(user_uuid))
                .filter(twofactor::atype.eq(atype))
                .first::<Self>(conn)
                .ok()
        })
        .await
    }

    pub async fn delete_all_by_user(user_uuid: &UserId, conn: &DbConn) -> EmptyResult {
        conn.run(move |conn| {
            diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(user_uuid)))
                .execute(conn)
                .map_res("Error deleting twofactors")
        })
        .await
    }

    pub async fn migrate_u2f_to_webauthn(conn: &DbConn) -> EmptyResult {
        use crate::api::core::two_factor::webauthn::{U2FRegistration, get_webauthn_registrations};
        use webauthn_rs::prelude::{COSEEC2Key, COSEKey, COSEKeyType, ECDSACurve};
        use webauthn_rs_proto::{COSEAlgorithm, UserVerificationPolicy};

        let u2f_factors = conn
            .run(move |conn| {
                twofactor::table
                    .filter(twofactor::atype.eq(TwoFactorType::U2f as i32))
                    .load::<Self>(conn)
                    .expect("Error loading twofactor")
            })
            .await;

        for mut u2f in u2f_factors {
            let mut regs: Vec<U2FRegistration> = serde_json::from_str(&u2f.data)?;
            // If there are no registrations or they are migrated (we do the migration in batch so we can consider them all migrated when the first one is)
            if regs.is_empty() || regs[0].migrated == Some(true) {
                continue;
            }

            let (_, mut webauthn_regs) = get_webauthn_registrations(&u2f.user_uuid, conn).await?;

            // If the user already has webauthn registrations saved, don't overwrite them
            if !webauthn_regs.is_empty() {
                continue;
            }

            for reg in &mut regs {
                let x: [u8; 32] = reg.reg.pub_key[1..33].try_into().unwrap();
                let y: [u8; 32] = reg.reg.pub_key[33..65].try_into().unwrap();

                let key = COSEKey {
                    type_: COSEAlgorithm::ES256,
                    key: COSEKeyType::EC_EC2(COSEEC2Key {
                        curve: ECDSACurve::SECP256R1,
                        x: x.into(),
                        y: y.into(),
                    }),
                };

                let new_reg = WebauthnRegistration {
                    id: reg.id,
                    migrated: true,
                    name: reg.name.clone(),
                    credential: Credential {
                        counter: reg.counter,
                        user_verified: false,
                        cred: key,
                        cred_id: reg.reg.key_handle.clone().into(),
                        registration_policy: UserVerificationPolicy::Discouraged_DO_NOT_USE,

                        transports: None,
                        backup_eligible: false,
                        backup_state: false,
                        extensions: RegisteredExtensions::none(),
                        attestation: ParsedAttestation::default(),
                        attestation_format: AttestationFormat::None,
                    }
                    .into(),
                };

                webauthn_regs.push(new_reg);

                reg.migrated = Some(true);
            }

            u2f.data = serde_json::to_string(&regs)?;
            u2f.save(conn).await?;

            TwoFactor::new(u2f.user_uuid.clone(), TwoFactorType::Webauthn, serde_json::to_string(&webauthn_regs)?)
                .save(conn)
                .await?;
        }

        Ok(())
    }

    pub async fn migrate_credential_to_passkey(conn: &DbConn) -> EmptyResult {
        let webauthn_factors = conn
            .run(move |conn| {
                twofactor::table
                    .filter(twofactor::atype.eq(TwoFactorType::Webauthn as i32))
                    .load::<Self>(conn)
                    .expect("Error loading twofactor")
            })
            .await;

        for webauthn_factor in webauthn_factors {
            // assume that a failure to parse into the old struct, means that it was already converted
            // alternatively this could also be checked via an extra field in the db
            let Ok(regs) = serde_json::from_str::<Vec<WebauthnRegistrationV3>>(&webauthn_factor.data) else {
                continue;
            };

            let regs = regs.into_iter().map(Into::into).collect::<Vec<WebauthnRegistration>>();

            TwoFactor::new(webauthn_factor.user_uuid.clone(), TwoFactorType::Webauthn, serde_json::to_string(&regs)?)
                .save(conn)
                .await?;
        }

        Ok(())
    }
}

#[derive(Clone, Debug, DieselNewType, FromForm, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TwoFactorId(String);

#[derive(Deserialize)]
pub struct WebauthnRegistrationV3 {
    pub id: i32,
    pub name: String,
    pub migrated: bool,
    pub credential: CredentialV3,
}

impl From<WebauthnRegistrationV3> for WebauthnRegistration {
    fn from(value: WebauthnRegistrationV3) -> Self {
        Self {
            id: value.id,
            name: value.name,
            migrated: value.migrated,
            credential: Credential::from(value.credential).into(),
        }
    }
}
