use crate::{MemberVote, RingspaceError, Standing, StandingResult, VoteSplit};
use serde::{Deserialize, Serialize};
use sqlx::{Error, FromRow, Sqlite, SqliteConnection, query, query_as, sqlite::SqliteQueryResult};

///
/// The payload sent to a Ring server for a rotation of
/// signing keys. The `signature` is made with the bytes of `old_key`
/// and the Member's private signing key. This serves as authentication
/// for the rotation request.
///
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct RotationRequestPayload {
    pub request: RotationRequest,
    pub signature: String,
}

///
/// The internal payload for a [RotationRequest]
///
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct RotationRequest {
    pub member_url: String,
    pub old_key: String,
    pub new_key: String,
}

impl RotationRequest {
    pub fn new(member_url: &str, old_key: &str, new_key: &str) -> Self {
        RotationRequest {
            member_url: member_url.to_string(),
            old_key: old_key.to_string(),
            new_key: new_key.to_string(),
        }
    }
}

///
/// Unenroll request
///
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UnenrollRequest {
    pub member_url: String,
}

impl UnenrollRequest {
    pub fn new(member_url: &str) -> Self {
        UnenrollRequest {
            member_url: member_url.to_string(),
        }
    }
}

///
/// Unenroll request container with signature
///
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UnenrollRequestPayload {
    pub request: UnenrollRequest,
    pub signature: String,
}

#[derive(FromRow, Serialize, Debug, Clone)]
pub struct Member {
    pub id: i64,
    pub ring_id: i64,
    pub name: String,
    pub url: String,
    pub description: String,
    pub manifest_version: i64,
    pub public_signing_key: String,
    pub enrolled: bool,
}

impl Member {
    pub fn new(
        name: String,
        url: String,
        description: String,
        manifest_version: i64,
        public_signing_key: String,
        ring_id: i64,
    ) -> Self {
        Member {
            id: 0,
            ring_id,
            name,
            url,
            description,
            manifest_version,
            public_signing_key,
            enrolled: true,
        }
    }

    ///
    /// Add/update Member in the database
    ///
    pub async fn save(&self, conn: &mut SqliteConnection) -> Result<SqliteQueryResult, Error> {
        // Add new if no ID
        if self.id == 0 {
            return query(
                    "
                        INSERT INTO members (name, url, description, manifest_version, public_signing_key, enrolled, ring_id)
                        VALUES (?, ?, ?, ?, ?, ?, ?)
                    ",
                )
                .bind(&self.name)
                .bind(&self.url)
                .bind(&self.description)
                .bind(self.manifest_version)
                .bind(&self.public_signing_key)
                .bind(self.enrolled)
                .bind(self.ring_id)
                .execute(&mut *conn)
                .await;
        }

        query(
            "
                UPDATE members SET
                    name = ?,
                    url = ?,
                    description = ?,
                    public_signing_key = ?,
                    enrolled = ?,
                    ring_id = ?
                WHERE id = ?
                ",
        )
        .bind(&self.name)
        .bind(&self.url)
        .bind(&self.description)
        .bind(&self.public_signing_key)
        .bind(self.enrolled)
        .bind(self.ring_id)
        .bind(self.id)
        .execute(&mut *conn)
        .await
    }

    ///
    /// Retrieve Member Votes
    ///
    pub async fn votes(&self, conn: &mut SqliteConnection) -> Result<Vec<MemberVote>, Error> {
        query_as::<Sqlite, MemberVote>(
            "
            SELECT * from member_votes
            WHERE member_id = ?
            AND ring_id = ?
            ",
        )
        .bind(self.id)
        .bind(self.ring_id)
        .fetch_all(&mut *conn)
        .await
    }

    ///
    /// Fetches all votes for a given Member URL and determines standing.
    ///
    /// The rules are:
    ///
    /// - No votes == Neutral
    /// - 1 vote: Decider
    /// - 2 votes: If split, take the result of the valences (i.e. Good + Bad = Neutral)
    /// - 3+ votes: map/reduce to determine value
    ///
    pub async fn standing(
        &self,
        conn: &mut SqliteConnection,
    ) -> Result<StandingResult, RingspaceError> {
        // Acquire votes
        let votes = self.votes(conn).await?;

        // Handle no votes
        if votes.is_empty() {
            return Ok(StandingResult {
                standing: Standing::Neutral,
                vote_split: VoteSplit {
                    good: 0,
                    neutral: 0,
                    bad: 0,
                },
                descriptions: vec![],
            });
        }

        // Extract descrptions from votes
        let descriptions: Vec<String> = votes.iter().map(|v| v.description.to_owned()).collect();

        // Fold votes into a VoteSplit
        let vote_split: VoteSplit = votes.iter().fold(
            VoteSplit {
                good: 0,
                bad: 0,
                neutral: 0,
            },
            |acc, e| match Standing::try_from(e.standing.clone())
                .expect("Couldn't convert standing")
            {
                Standing::Good => VoteSplit {
                    good: acc.good + 1,
                    bad: acc.bad,
                    neutral: acc.neutral,
                },
                Standing::Bad => VoteSplit {
                    good: acc.good,
                    bad: acc.bad + 1,
                    neutral: acc.neutral,
                },
                Standing::Neutral => VoteSplit {
                    good: acc.good,
                    bad: acc.bad,
                    neutral: acc.neutral + 1,
                },
            },
        );

        // Assign standing
        let standing = if vote_split.good >= vote_split.bad && vote_split.good >= vote_split.neutral
        {
            Standing::Good
        } else if vote_split.bad >= vote_split.neutral {
            Standing::Bad
        } else {
            Standing::Neutral
        };

        Ok(StandingResult {
            standing,
            vote_split,
            descriptions,
        })
    }
}

///
/// List all members for a given `ring_id`.
///
pub async fn list(conn: &mut SqliteConnection, ring_id: i64) -> Result<Vec<Member>, Error> {
    query_as::<sqlx::Sqlite, Member>(
        "
        SELECT * FROM members where ring_id = ?      
        ",
    )
    .bind(ring_id)
    .fetch_all(&mut *conn)
    .await
}

///
/// List all members, regardless of Ring.
///
pub async fn list_all(conn: &mut SqliteConnection) -> Result<Vec<Member>, Error> {
    query_as::<sqlx::Sqlite, Member>(
        "
        SELECT * FROM members      
        ",
    )
    .fetch_all(&mut *conn)
    .await
}

///
/// Get a single member by its ID.
///  
pub async fn get_by_id(conn: &mut SqliteConnection, member_id: i64) -> Result<Member, Error> {
    query_as::<sqlx::Sqlite, Member>(
        "
        SELECT * FROM members where id = ?      
        ",
    )
    .bind(member_id)
    .fetch_one(&mut *conn)
    .await
}

///
/// Get a single memebr by its URL.
///  
pub async fn get_by_url(
    conn: &mut SqliteConnection,
    url: &str,
    ring_id: i64,
) -> Result<Member, Error> {
    query_as::<sqlx::Sqlite, Member>(
        "
        SELECT * FROM members where url = ? AND ring_id = ?      
        ",
    )
    .bind(url)
    .bind(ring_id)
    .fetch_one(&mut *conn)
    .await
}
