use std::{borrow::Cow, str::FromStr, sync::Arc};

use async_graphql::{Description, InputValueError, InputValueResult, Scalar, ScalarType, Value};
use chrono::{DateTime, Utc};
use futures::{StreamExt as _, TryStreamExt as _};
use serde::{de::DeserializeOwned, Deserialize, Serialize};

use qm_mongodb::{
    bson::{doc, oid::ObjectId, serde_helpers::chrono_datetime_as_bson_datetime, Document, Uuid},
    options::FindOptions,
    Collection, Database,
};

use crate::{
    error::EntityError,
    ids::{
        InstitutionId, InstitutionResourceId, OrganizationId, OrganizationOrInstitution,
        OrganizationResourceId, OwnerId,
    },
    model::ListFilter,
};

const EMPTY_ID: &str = "000000000000000000000000";
const DEFAULT_PAGE_LIMIT: i64 = 100;

#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize, Description)]
pub struct Id(ObjectId);

impl FromStr for Id {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        parse_object_id(s)?.ok_or(())
    }
}

fn parse_object_id(id: &str) -> Result<Option<Id>, ()> {
    if id == EMPTY_ID {
        Ok(None)
    } else {
        Ok(Some(ObjectId::from_str(id).map(Id).map_err(|_| ())?))
    }
}

#[Scalar]
impl ScalarType for Id {
    fn parse(value: Value) -> InputValueResult<Self> {
        if let Value::String(value) = &value {
            Ok(Self::from_str(value).map_err(|_| InputValueError::custom("parse error"))?)
        } else {
            Err(InputValueError::expected_type(value))
        }
    }

    fn to_value(&self) -> Value {
        Value::String(self.0.to_hex())
    }
}

type ID = Id;

#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize, Description)]
#[graphql(name = "EntityId")]
pub struct GraphQLId {
    #[graphql(flatten)]
    id: ID,
}

impl FromStr for GraphQLId {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        parse_object_id(s)?.map(|id| Self { id }).ok_or(())
    }
}

impl AsRef<ObjectId> for GraphQLId {
    fn as_ref(&self) -> &ObjectId {
        &self.id.0
    }
}

#[Scalar]
impl ScalarType for GraphQLId {
    fn parse(value: Value) -> InputValueResult<Self> {
        if let Value::String(value) = &value {
            Ok(Self::from_str(value).map_err(|_| InputValueError::custom("parse error"))?)
        } else {
            Err(InputValueError::expected_type(value))
        }
    }

    fn to_value(&self) -> Value {
        Value::String(self.id.0.to_hex())
    }
}

impl From<Id> for GraphQLId {
    fn from(id: Id) -> Self {
        Self { id }
    }
}

pub trait ToMongoFilterMany {
    fn to_mongo_filter_many(&self) -> Option<Document>;
}

impl ToMongoFilterMany for () {
    fn to_mongo_filter_many(&self) -> Option<Document> {
        None
    }
}

impl ToMongoFilterMany for Option<Document> {
    fn to_mongo_filter_many(&self) -> Option<Document> {
        self.clone()
    }
}

impl ToMongoFilterMany for InstitutionId {
    fn to_mongo_filter_many(&self) -> Option<Document> {
        let (cid, oid, iid) = self.unzip();
        Some(doc! { "owner.cid": cid, "owner.oid": oid, "owner.iid": iid })
    }
}

impl ToMongoFilterMany for OrganizationId {
    fn to_mongo_filter_many(&self) -> Option<Document> {
        let (cid, oid) = self.unzip();
        Some(doc! { "owner.cid": cid, "owner.oid": oid })
    }
}

impl ToMongoFilterMany for Option<OrganizationId> {
    fn to_mongo_filter_many(&self) -> Option<Document> {
        self.as_ref().and_then(|v| v.to_mongo_filter_many())
    }
}

impl ToMongoFilterMany for OrganizationOrInstitution {
    fn to_mongo_filter_many(&self) -> Option<Document> {
        match self {
            Self::Institution(v) => v.to_mongo_filter_many(),
            Self::Organization(v) => v.to_mongo_filter_many(),
        }
    }
}

impl ToMongoFilterMany for Option<OrganizationOrInstitution> {
    fn to_mongo_filter_many(&self) -> Option<Document> {
        self.as_ref().and_then(|v| v.to_mongo_filter_many())
    }
}

pub trait ToMongoFilterOne {
    fn to_mongo_filter_one(&self) -> Document;
}

impl ToMongoFilterOne for Document {
    fn to_mongo_filter_one(&self) -> Document {
        self.clone()
    }
}

impl ToMongoFilterOne for InstitutionResourceId {
    fn to_mongo_filter_one(&self) -> Document {
        let (cid, oid, iid, id) = self.unzip();
        doc! { "owner.cid": cid, "owner.oid": oid, "owner.iid": iid, "_id": id }
    }
}

impl ToMongoFilterOne for OrganizationResourceId {
    fn to_mongo_filter_one(&self) -> Document {
        let (cid, oid, id) = self.unzip();
        doc! { "owner.cid": cid, "owner.oid": oid, "_id": id }
    }
}

impl ToMongoFilterOne for OrganizationId {
    fn to_mongo_filter_one(&self) -> Document {
        let (cid, oid) = self.unzip();
        doc! { "owner.cid": cid, "_id": oid }
    }
}

impl ToMongoFilterOne for InstitutionId {
    fn to_mongo_filter_one(&self) -> Document {
        let (cid, oid, iid) = self.unzip();
        doc! { "owner.cid": cid, "owner.oid": oid, "_id": iid }
    }
}

impl ToMongoFilterOne for OrganizationOrInstitution {
    fn to_mongo_filter_one(&self) -> Document {
        match self {
            Self::Institution(v) => v.to_mongo_filter_one(),
            Self::Organization(v) => v.to_mongo_filter_one(),
        }
    }
}

pub trait ToMongoFilterExact {
    fn to_mongo_filter_exact(&self) -> Result<Document, EntityError>;
}

pub struct ResourcesFilter<'a, I>(pub &'a [I])
where
    I: ToMongoFilterOne;
impl<'a, I> ToMongoFilterExact for ResourcesFilter<'a, I>
where
    I: ToMongoFilterOne,
{
    fn to_mongo_filter_exact(&self) -> Result<Document, EntityError> {
        if self.0.is_empty() {
            return Err(EntityError::NotEmpty);
        }
        if self.0.len() == 1 {
            return Ok(self.0.first().unwrap().to_mongo_filter_one());
        }
        let items: Vec<Document> = self.0.iter().map(|v| v.to_mongo_filter_one()).collect();
        Ok(doc! {
            "$or": items,
        })
    }
}

pub trait AsMongoId {
    fn as_mongo_id(&self) -> ObjectId;
}

#[derive(Debug, Deserialize, Serialize)]
pub struct Entity<T> {
    id: ID,
    #[serde(flatten)]
    fields: T,
    #[serde(flatten)]
    defaults: Defaults,
}

#[derive(Debug, Default, Deserialize, Serialize)]
pub struct Page<I> {
    pub items: Vec<I>,
    pub skip: u64,
    pub limit: Option<i64>,
    pub total: usize,
}

impl<I> Page<I> {
    /// Empty page.
    pub fn empty() -> Self {
        Self {
            items: vec![],
            total: 0,
            skip: 0,
            limit: Some(DEFAULT_PAGE_LIMIT),
        }
    }

    /// Returns page index.
    pub fn index(&self) -> u64 {
        if let Some(limit) = self.limit.filter(|l| *l > 0).map(|l| l as u64) {
            self.skip / limit
        } else {
            0
        }
    }

    /// Returns page count.
    pub fn count(&self) -> usize {
        if let Some(limit) = self.limit.filter(|l| *l > 0).map(|l| l as usize) {
            (self.total + (limit - 1)) / limit
        } else {
            0
        }
    }
}

#[derive(Default)]
pub struct PageInfo {
    skip: Option<u64>,
    limit: Option<i64>,
}

impl TryFrom<ListFilter> for PageInfo {
    type Error = EntityError;

    fn try_from(value: ListFilter) -> Result<Self, Self::Error> {
        let limit = value.limit.map(|l| l as i64).unwrap_or(DEFAULT_PAGE_LIMIT);
        Ok(Self {
            skip: value.page.map(|page| limit as u64 * page as u64),
            limit: Some(limit),
        })
    }
}

impl TryFrom<Option<ListFilter>> for PageInfo {
    type Error = EntityError;

    fn try_from(value: Option<ListFilter>) -> Result<Self, Self::Error> {
        value
            .map(|v| v.try_into())
            .unwrap_or_else(|| Ok(Default::default()))
    }
}

pub trait UpdateEntity<T: Clone> {
    fn update_entity(self, entity: &T) -> Result<Cow<T>, EntityError>;
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct EntityOwned<T, ID = Id> {
    #[serde(rename = "_id")]
    pub id: ID,
    pub owner: Arc<OwnerId>,
    #[serde(flatten)]
    pub fields: T,
    #[serde(flatten)]
    pub defaults: Arc<Defaults>,
}

impl<T> EntityOwned<T>
where
    T: DeserializeOwned + Serialize + MongoCollection + Send + Sync + Unpin,
{
    pub async fn create(
        db: &Database,
        owner: impl Into<OwnerId>,
        fields: T,
        user_id: Uuid,
    ) -> Result<Self, EntityError> {
        #[derive(Serialize)]
        struct CreateOwnedEntity<'f, F> {
            owner: Arc<OwnerId>,
            #[serde(flatten)]
            fields: &'f F,
            #[serde(flatten)]
            defaults: Arc<Defaults>,
        }

        let owner = Arc::new(owner.into());
        let defaults = Arc::new(Defaults::now(user_id));

        T::mongo_collection(db)
            .insert_one(CreateOwnedEntity {
                owner: owner.clone(),
                fields: &fields,
                defaults: defaults.clone(),
            })
            .await?
            .inserted_id
            .as_object_id()
            .map(Id)
            .ok_or(EntityError::NoId)
            .map(|id| Self {
                id,
                owner,
                fields,
                defaults,
            })
    }
}

impl<T, ID> EntityOwned<T, ID>
where
    T: DeserializeOwned + Serialize + MongoCollection + Send + Sync + Unpin,
    ID: DeserializeOwned + Serialize + Send + Sync + Unpin,
{
    pub async fn list(
        db: &Database,
        filter: impl ToMongoFilterMany,
    ) -> Result<Vec<Self>, EntityError> {
        T::mongo_collection(db)
            .find(filter.to_mongo_filter_many().unwrap_or_default())
            .await?
            .try_collect()
            .await
            .map_err(From::from)
    }

    pub async fn page(
        db: &Database,
        filter: impl ToMongoFilterMany,
        page_selector: impl TryInto<PageInfo, Error = EntityError>,
    ) -> Result<Page<Self>, EntityError> {
        Self::page_filter(
            db,
            filter.to_mongo_filter_many().unwrap_or_default(),
            page_selector,
        )
        .await
    }

    pub async fn list_exact(
        db: &Database,
        filter: impl ToMongoFilterExact,
    ) -> Result<Vec<Self>, EntityError> {
        T::mongo_collection(db)
            .find(filter.to_mongo_filter_exact()?)
            .await?
            .try_collect()
            .await
            .map_err(From::from)
    }

    pub async fn page_exact(
        db: &Database,
        filter: impl ToMongoFilterExact,
        page_selector: impl TryInto<PageInfo, Error = EntityError>,
    ) -> Result<Page<Self>, EntityError> {
        Self::page_filter(db, filter.to_mongo_filter_exact()?, page_selector).await
    }

    pub async fn by_id(
        db: &Database,
        id: impl ToMongoFilterOne,
    ) -> Result<Option<Self>, EntityError> {
        T::mongo_collection(db)
            .find_one(id.to_mongo_filter_one())
            .await
            .map_err(From::from)
    }

    pub async fn update(
        db: &Database,
        context: impl ToMongoFilterOne,
        input: impl UpdateEntity<T>,
        user_id: Uuid,
    ) -> Result<Self, EntityError>
    where
        T: Clone,
    {
        let filter = context.to_mongo_filter_one();
        let Some(mut entity): Option<Self> =
            T::mongo_collection(db).find_one(filter.clone()).await?
        else {
            return Err(EntityError::NotFound);
        };

        if let Cow::Owned(updated) = input.update_entity(&entity.fields)? {
            entity.fields = updated;
            entity.defaults = Arc::new(entity.defaults.update_by(user_id));

            if let Some(filter) = filter.into() {
                T::mongo_collection::<Self>(db)
                    .replace_one(filter, &entity)
                    .await?;
            }
        }

        Ok(entity)
    }

    pub async fn save<C>(
        db: &Database,
        context: C,
        input: impl Into<T>,
        user_id: Uuid,
    ) -> Result<bool, EntityError>
    where
        T: Clone + std::fmt::Debug,
        C: ToMongoFilterOne + Into<OwnerId>,
    {
        let filter = context.to_mongo_filter_one();
        #[derive(Debug, Serialize)]
        struct SaveEntity<F> {
            owner: OwnerId,
            #[serde(flatten)]
            fields: F,
            #[serde(flatten)]
            defaults: Arc<Defaults>,
        }
        let defaults = Arc::new(Defaults::now(user_id));
        let entity = SaveEntity {
            owner: context.into(),
            fields: input.into(),
            defaults,
        };
        let result = T::mongo_collection::<SaveEntity<_>>(db)
            .replace_one(filter, &entity)
            .upsert(true)
            .await?;
        Ok(result.modified_count > 0 || result.upserted_id.is_some())
    }

    pub async fn remove<I>(db: &Database, ids: I) -> Result<i32, EntityError>
    where
        I: ToMongoFilterExact,
    {
        let result = T::mongo_collection::<Document>(db)
            .delete_many(ids.to_mongo_filter_exact()?)
            .await?;
        Ok(result.deleted_count as i32)
    }

    pub async fn page_filter(
        db: &Database,
        filter: Document,
        page_selector: impl TryInto<PageInfo, Error = EntityError>,
    ) -> Result<Page<Self>, EntityError> {
        let total = T::mongo_collection::<Self>(db)
            .find(filter.clone())
            .await?
            .count()
            .await;

        if total == 0 {
            return Ok(Page::empty());
        }

        let page_info: PageInfo = page_selector.try_into()?;

        let limit = page_info.limit;

        T::mongo_collection(db)
            .find(filter)
            .with_options(
                FindOptions::builder()
                    .limit(limit)
                    .skip(page_info.skip)
                    .build(),
            )
            .await?
            .try_collect()
            .await
            .map(|items| Page {
                items,
                total,
                skip: page_info.skip.unwrap_or_default(),
                limit,
            })
            .map_err(From::from)
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct Defaults {
    pub created: UserModification,
    pub modified: UserModification,
}

impl Defaults {
    pub fn now(user_id: Uuid) -> Self {
        let modify = UserModification::now(user_id);
        Self {
            created: modify.clone(),
            modified: modify,
        }
    }

    pub fn update_by(&self, user_id: Uuid) -> Self {
        let modified = UserModification::now(user_id);
        Self {
            created: self.created.clone(),
            modified,
        }
    }
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UserModification {
    #[serde(rename = "uid")]
    pub user_id: Uuid,
    #[serde(with = "chrono_datetime_as_bson_datetime")]
    pub at: DateTime<Utc>,
}

impl UserModification {
    pub fn now(user_id: Uuid) -> Self {
        Self {
            user_id,
            at: Utc::now(),
        }
    }
}

pub trait EntityField {
    type Field<T: Serialize + DeserializeOwned>: Serialize + DeserializeOwned;
}

#[derive(Default, Clone, PartialEq, Debug)]
pub struct Optional;
impl EntityField for Optional {
    type Field<T: Serialize + DeserializeOwned> = Option<T>;
}

#[derive(Default, Clone, PartialEq, Debug)]
pub struct Required;
impl EntityField for Required {
    type Field<T: Serialize + DeserializeOwned> = T;
}

pub trait MongoCollection {
    const COLLECTION: &'static str;

    fn mongo_collection<T>(db: &Database) -> Collection<T>
    where
        T: Send + Sync,
    {
        db.collection(Self::COLLECTION)
    }
}
