// Copyright 2021-2022 AUDITOR developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

use super::{Score, ScoreTest, ValidAmount, ValidName};
use anyhow::{Context, Error};
use fake::{Dummy, Fake, Faker, StringFaker};
use rand::Rng;
use serde::{Deserialize, Serialize};
use sqlx::{
    postgres::{PgHasArrayType, PgTypeInfo},
    Postgres, Type,
};

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::Encode, Clone, PartialOrd, Ord)]
#[sqlx(type_name = "component")]
pub struct Component {
    pub name: ValidName,
    pub amount: ValidAmount,
    pub scores: Vec<Score>,
}

impl Component {
    pub fn new<T: AsRef<str>>(name: T, amount: i64) -> Result<Self, Error> {
        Ok(Component {
            name: ValidName::parse(name.as_ref().to_string())
                .context("Failed to parse component name.")?,
            amount: ValidAmount::parse(amount).context("Failed to parse component amount.")?,
            scores: vec![],
        })
    }

    pub fn with_score(mut self, score: Score) -> Self {
        self.scores.push(score);
        self
    }

    pub fn with_scores(mut self, mut scores: Vec<Score>) -> Self {
        self.scores.append(&mut scores);
        self
    }
}

// manual impl of decode because of a compiler bug. See:
// https://github.com/launchbadge/sqlx/issues/1031
// https://github.com/rust-lang/rust/issues/82219
impl sqlx::decode::Decode<'_, sqlx::Postgres> for Component {
    fn decode(
        value: sqlx::postgres::PgValueRef<'_>,
    ) -> Result<Self, std::boxed::Box<dyn std::error::Error + 'static + Send + Sync>> {
        let mut decoder = sqlx::postgres::types::PgRecordDecoder::new(value)?;
        let name = decoder.try_decode::<ValidName>()?;
        let amount = decoder.try_decode::<ValidAmount>()?;
        let scores = decoder.try_decode::<Vec<Score>>()?;
        Ok(Component {
            name,
            amount,
            scores,
        })
    }
}

impl Type<Postgres> for Component {
    fn type_info() -> PgTypeInfo {
        PgTypeInfo::with_name("component")
    }
}

impl PgHasArrayType for Component {
    fn array_type_info() -> PgTypeInfo {
        PgTypeInfo::with_name("_component")
    }
}

impl TryFrom<ComponentTest> for Component {
    type Error = Error;

    fn try_from(value: ComponentTest) -> Result<Self, Self::Error> {
        Ok(Component {
            name: ValidName::parse(value.name.ok_or_else(|| anyhow::anyhow!("name is None"))?)?,
            amount: ValidAmount::parse(
                value
                    .amount
                    .ok_or_else(|| anyhow::anyhow!("amount is None"))?,
            )?,
            scores: value
                .scores
                .into_iter()
                .map(Score::try_from)
                .collect::<Result<_, Self::Error>>()?,
        })
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ComponentTest {
    pub name: Option<String>,
    pub amount: Option<i64>,
    // Vecs can be empty, therefore no option needed
    pub scores: Vec<ScoreTest>,
}

impl PartialEq<Component> for ComponentTest {
    fn eq(&self, other: &Component) -> bool {
        let ComponentTest {
            name: s_name,
            amount: s_amount,
            scores: s_scores,
        } = self;
        let Component {
            name: o_name,
            amount: o_amount,
            scores: o_scores,
        } = other;

        // Can't be equal if any field in ComponentTest is None
        if s_name.is_none() || s_amount.is_none() {
            return false;
        }

        let mut s_scores = s_scores.clone();
        let mut o_scores = o_scores.clone();

        s_scores.sort();
        o_scores.sort();

        s_name.as_ref().unwrap() == o_name.as_ref()
            && s_amount.as_ref().unwrap() == o_amount.as_ref()
            && s_scores
                .into_iter()
                .zip(o_scores.into_iter())
                .fold(true, |acc, (a, b)| acc && a == b)
    }
}

impl PartialEq<ComponentTest> for Component {
    fn eq(&self, other: &ComponentTest) -> bool {
        other.eq(self)
    }
}

impl Dummy<Faker> for ComponentTest {
    fn dummy_with_rng<R: Rng + ?Sized>(_: &Faker, rng: &mut R) -> ComponentTest {
        let name = StringFaker::with(
            String::from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789*&^%$#@!~")
                .into_bytes(),
            1..256,
        )
        .fake_with_rng(rng);
        ComponentTest {
            name: Some(name),
            amount: Some((0..i64::MAX).fake_with_rng(rng)),
            scores: (0..(0..10u64).fake_with_rng(rng))
                .into_iter()
                .map(|_| Faker.fake_with_rng::<ScoreTest, _>(rng))
                .collect(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use claim::assert_ok;

    impl quickcheck::Arbitrary for ComponentTest {
        fn arbitrary(_g: &mut quickcheck::Gen) -> Self {
            Faker.fake()
        }
    }

    #[quickcheck]
    fn a_valid_name_is_parsed_successfully(component: ComponentTest) {
        assert_ok!(Component::try_from(component));
    }
}
