use crate::membership::Membership;
use crate::site_vote::SiteVote;
use crate::{
    InviteResponse, JoinedRing, Keypair, MANIFEST_VERSION, generate_signing_keypair, joined_ring,
    jwks::{Jwks, ed25519_public_key_to_jwk, x25519_public_key_to_jwk},
    sign_value,
};
use crate::{
    MemberManifest, MemberManifestResponse, RingspaceError, RotationRequest,
    RotationRequestPayload, UnenrollRequest, UnenrollRequestPayload,
};
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD as BASE64URL};
use reqwest::{Client, StatusCode};
use serde::Serialize;
use serde_json::{Map, Value, json, to_string_pretty, to_value};
use sqlx::{Error, FromRow, Sqlite, SqliteConnection, query, query_as, sqlite::SqliteQueryResult};

/// A single website. This struct represents a site from the site
/// owner's perspective. Contrast this with [Member], which considers
/// the site from the Ring's perspective.
///
#[derive(FromRow, Serialize, Debug, Clone)]
pub struct Site {
    pub id: i64,
    pub name: String,
    pub url: String,
    pub description: String,
    pub manifest_version: i64,
    pub public_signing_key: String,
    pub private_signing_key: String,
    pub default_site: bool,
}

impl Site {
    pub fn new(name: String, url: String, description: String, default_site: bool) -> Self {
        let signing_keypair = generate_signing_keypair();

        Site {
            id: 0,
            name,
            url,
            description,
            manifest_version: MANIFEST_VERSION,
            public_signing_key: signing_keypair.public_key,
            private_signing_key: signing_keypair.private_key,
            default_site,
        }
    }

    ///
    /// Create a new site from given `url`, `name`, `description`, and [Keypair].
    ///
    pub async fn save(&self, conn: &mut SqliteConnection) -> Result<SqliteQueryResult, Error> {
        // If default, clear existing defaults
        if self.default_site {
            query(
                "
                UPDATE sites
                SET default_site = false;
                ",
            )
            .execute(&mut *conn)
            .await?;
        }

        // Add new if no id
        if self.id == 0 {
            return query(
                "
                INSERT INTO sites (
                    name,
                    url,
                    description,
                    manifest_version,
                    public_signing_key,
                    private_signing_key,
                    default_site
                )
                VALUES (?, ?, ?, ?, ?, ?, ?)
                ",
            )
            .bind(&self.name)
            .bind(&self.url)
            .bind(&self.description)
            .bind(self.manifest_version)
            .bind(&self.public_signing_key)
            .bind(&self.private_signing_key)
            .bind(self.default_site)
            .execute(&mut *conn)
            .await;
        }

        // Otherwise, update
        query(
            "
            UPDATE sites SET
                name = ?,
                url = ?,
                description = ?,
                manifest_version = ?,
                public_signing_key = ?,
                private_signing_key = ?,
                default_site = ?
            WHERE id = ?
            ",
        )
        .bind(&self.name)
        .bind(&self.url)
        .bind(&self.description)
        .bind(self.manifest_version)
        .bind(&self.public_signing_key)
        .bind(&self.private_signing_key)
        .bind(self.default_site)
        .bind(self.id)
        .execute(&mut *conn)
        .await
    }

    ///
    /// Delete Site
    ///
    pub async fn delete(&self, conn: &mut SqliteConnection) -> Result<SqliteQueryResult, Error> {
        query(
            "
            DELETE FROM sites
            WHERE id = ?;
            DELETE FROM memberships
            WHERE site_id = ?;
            ",
        )
        .bind(self.id)
        .bind(self.id)
        .execute(&mut *conn)
        .await
    }

    ///
    /// Reach out to a Ringspace server with an invite code to join.
    /// Returns ring data for creating a local record of the ring and
    /// its connection to the site.
    ///
    pub async fn join_ring(
        &self,
        ring_url: &str,
        invite_code: &str,
    ) -> Result<InviteResponse, RingspaceError> {
        let client = Client::new();
        let url = format!("{ring_url}/api/invite/redeem");
        let jwk = ed25519_public_key_to_jwk(&self.public_signing_key)?;
        let body = json!({
            "url": self.url,
            "keyset": Jwks { keys: vec![jwk]},
            "name": self.name,
            "description": self.description,
            "manifest_version": self.manifest_version,
            "invite_code": invite_code
        });
        let res = client.post(url).json(&body).send().await?;
        if res.status().is_success() {
            let ring_data: InviteResponse = res.json().await?;
            println!("{}", to_string_pretty(&ring_data)?);
            Ok(ring_data)
        } else {
            Err(RingspaceError {
                msg: res.text().await?,
            })
        }
    }

    ///
    /// Requests unenrollment from a [JoinedRing].
    /// On success, the local [Membership] and [JoinedRing]
    /// entries are removed.
    ///
    pub async fn leave_ring(
        &self,
        conn: &mut SqliteConnection,
        ring_url: &str,
    ) -> Result<(), RingspaceError> {
        let ring = joined_ring::get_by_url(conn, ring_url).await?;
        let client = Client::new();
        let url = format!("{ring_url}/api/member/unenroll");
        let request = UnenrollRequest::new(&self.url);
        let request_value = to_value(&request)?;
        let signature = sign_value(&request_value, &self.private_signing_key)?;
        let signature_bytes = signature.to_bytes();
        let signature = BASE64URL.encode(signature_bytes);
        let request_payload = UnenrollRequestPayload { request, signature };
        let res = client.post(url).json(&request_payload).send().await?;
        if res.status().is_success() {
            // Run the DB query to kill membership and the JoinedRing.
            // Is this a little kludgier than using a method for membership and ring?
            // Maybe, but we both Ids in hand, it's safe.
            query(
                "
                DELETE FROM memberships
                WHERE site_id = ? AND ring_id = ?;
                ",
            )
            .bind(self.id)
            .bind(ring.id)
            .bind(ring.id)
            .execute(&mut *conn)
            .await?;
            Ok(())
        } else {
            Err(RingspaceError {
                msg: res.text().await?,
            })
        }
    }

    ///
    /// Retrieves all [SiteVote]s for a given site
    ///
    pub async fn votes(&self, conn: &mut SqliteConnection) -> Result<Vec<SiteVote>, Error> {
        query_as::<Sqlite, SiteVote>(
            "
            SELECT * FROM site_votes
            WHERE site_id = ?
            ORDER BY timestamp DESC
            ",
        )
        .bind(self.id)
        .fetch_all(&mut *conn)
        .await
    }

    ///
    /// Retrieve Memberships
    ///
    pub async fn memberships(&self, conn: &mut SqliteConnection) -> Result<Vec<Membership>, Error> {
        query_as::<Sqlite, Membership>(
            "
            SELECT * FROM memberships
            WHERE site_id = ?
            ",
        )
        .bind(self.id)
        .fetch_all(&mut *conn)
        .await
    }

    ///
    /// Retrieve a Site's [JoinedRing]s
    ///
    pub async fn rings(
        &self,
        conn: &mut SqliteConnection,
    ) -> Result<Vec<JoinedRing>, RingspaceError> {
        let memberships = self.memberships(conn).await?;
        let mut rings: Vec<JoinedRing> = Vec::new();
        for m in memberships {
            let ring = joined_ring::get_by_id(conn, m.ring_id).await?;
            rings.push(ring);
        }
        Ok(rings)
    }

    ///
    /// Generate's a Site's Manifest
    ///
    pub async fn manifest(
        &self,
        conn: &mut SqliteConnection,
    ) -> Result<MemberManifestResponse, RingspaceError> {
        // Get Memberships

        let memberships = self.clone().memberships(conn).await?;
        let votes = self.clone().votes(conn).await?;

        let mut rings_with_votes = Map::<String, Value>::new();

        for m in memberships {
            // Get vote_subject URLs. We need these to grab each subject's votes in turn
            let ring_votes: Vec<SiteVote> = votes
                .clone()
                .into_iter()
                .filter(|v| v.ring_id == m.ring_id)
                .collect();

            let ring = joined_ring::get_by_id(conn, m.ring_id).await?;

            let mut votes_map = Map::<String, Value>::new();

            for v in ring_votes {
                // This confirms that we get one vote per URL, and since
                // the votes are ordered by descending date, it also means
                // we get the latest one
                let url_vec: Vec<String> = votes_map.keys().map(|k| k.to_owned()).collect();
                if url_vec.contains(&v.vote_subject_url) {
                    continue;
                }
                let encrypted_vote = v.clone().encrypt(&ring.public_encryption_key).await?;
                votes_map.insert(v.vote_subject_url.clone(), to_value(&encrypted_vote)?);
            }

            let sig_jwk = ed25519_public_key_to_jwk(&ring.public_signing_key)?;
            let enc_jwk = x25519_public_key_to_jwk(&ring.public_encryption_key)?;

            votes_map.sort_keys();
            let mut ring_data = json!({
                "name": ring.name,
                "description": ring.description,
                "keyset": Jwks {
                    keys: vec![sig_jwk, enc_jwk]
                },
                "votes": votes_map
            });
            ring_data.sort_all_objects();
            rings_with_votes.insert(ring.url, ring_data);
        }

        rings_with_votes.sort_keys();

        let manifest = MemberManifest::new(
            &self.name,
            &self.url,
            &self.description,
            &self.public_signing_key,
            rings_with_votes,
        )?;

        let manifest_value = to_value(&manifest)?;
        let signature = sign_value(&manifest_value, &self.private_signing_key)?;
        let signature = BASE64URL.encode(signature.to_bytes());

        Ok(MemberManifestResponse {
            manifest,
            signature,
        })
    }

    ///
    /// Sends off the rotation request to the joined ring. We do not
    /// set the new key here, as that would be a side-effect. Instead, we return the
    /// new [Keypair] to be set in the main body of the operation.
    ///
    pub async fn request_rotation(
        &self,
        conn: &mut SqliteConnection,
    ) -> Result<Keypair, RingspaceError> {
        let new_keypair: Keypair = generate_signing_keypair();
        // Build payload with signature
        let request =
            RotationRequest::new(&self.url, &self.public_signing_key, &new_keypair.public_key);
        let mut request_value = to_value(&request)?;
        request_value.sort_all_objects();
        let signature = sign_value(&request_value, &self.private_signing_key)?;
        let signature = BASE64URL.encode(signature.to_bytes());
        let payload = RotationRequestPayload { request, signature };

        // Get Rings
        let memberships = self.clone().memberships(conn).await?;
        let mut successes: Vec<String> = Vec::new();
        let mut failures: Vec<String> = Vec::new();
        let client = Client::new();

        // Loop through members/rings to attempt rotation
        // Send rotation off first to all Rings we're a member of
        for m in memberships {
            let ring = joined_ring::get_by_id(conn, m.ring_id).await?;
            let url = format!("{}/api/member/rotate", ring.url);
            println!("Requestion rotation for {}", ring.url);

            // Send POST
            let res = client.post(&url).json(&json!(payload)).send().await;
            match res {
                Ok(r) => {
                    if r.status() == StatusCode::ACCEPTED {
                        println!("Rotation accepted for {}", ring.url);
                        successes.push(ring.url);
                    } else {
                        println!("Rotation failed for {}: {}", ring.url, r.text().await?);
                        failures.push(ring.url);
                    }
                }
                Err(e) => {
                    println!("Rotation error: {e}");
                    failures.push(ring.url);
                }
            }
        }

        // If any failures, roll back the successes
        if !failures.is_empty() {
            println!("Rolling back changes");
            for s in successes {
                let url = format!("{}/api/member/rotate", s);
                // Build payload with signature
                let request = RotationRequest {
                    member_url: self.clone().url,
                    old_key: new_keypair.clone().public_key.to_string(),
                    new_key: self.clone().public_signing_key,
                };
                let request_value = to_value(request.clone())?;
                let signature = sign_value(&request_value, &self.private_signing_key)?.to_string();
                let payload = RotationRequestPayload { request, signature };
                let res = client.post(url).json(&json!(payload)).send().await;
                if res.is_ok_and(|f| f.status() == StatusCode::ACCEPTED) {
                    println!("Rollback accepted for {s}");
                } else {
                    println!("Rollback failed for {s}");
                    println!("Contact site owner to replace keys");
                }
            }
            return Err(RingspaceError {
                msg: "Rotation failure".to_string(),
            });
        }

        // Success! Return the Keypair
        Ok(new_keypair)
    }
}

///
/// List all known sites
///
pub async fn list(conn: &mut SqliteConnection) -> Result<Vec<Site>, Error> {
    query_as::<Sqlite, Site>(
        "
        SELECT * FROM sites
        ",
    )
    .fetch_all(&mut *conn)
    .await
}

///
/// Retrieve site by id
///
pub async fn get_by_id(conn: &mut SqliteConnection, site_id: i64) -> Result<Site, Error> {
    query_as::<Sqlite, Site>(
        "
        SELECT * FROM sites WHERE id = ?
        ",
    )
    .bind(site_id)
    .fetch_one(&mut *conn)
    .await
}

///
/// Retrieve site by URL.
///
pub async fn get_by_url(conn: &mut SqliteConnection, url: &str) -> Result<Site, Error> {
    query_as::<Sqlite, Site>(
        "
        SELECT * FROM sites WHERE url = ?
        ",
    )
    .bind(url)
    .fetch_one(&mut *conn)
    .await
}

///
/// Get Default Site
///
pub async fn get_default(conn: &mut SqliteConnection) -> Result<Site, Error> {
    query_as::<Sqlite, Site>(
        "
        SELECT * FROM sites
        WHERE default_site = true;
        ",
    )
    .fetch_one(&mut *conn)
    .await
}
