use futures::stream::{self, TryStreamExt};
use serde::Serialize;
use std::fmt::Debug;
use std::rc::Rc;
use thiserror::Error;

use sqlparser::ast::{Assignment, Ident};

use super::context::FilterContext;
use super::evaluate::{evaluate, Evaluated};
use crate::data::{Row, Value};
use crate::result::Result;
use crate::store::Store;

#[derive(Error, Serialize, Debug, PartialEq)]
pub enum UpdateError {
    #[error("column not found {0}")]
    ColumnNotFound(String),

    #[error("conflict on schema, row data does not fit to schema")]
    ConflictOnSchema,

    #[error("unreachable")]
    Unreachable,
}

pub struct Update<'a, T: 'static + Debug> {
    storage: &'a dyn Store<T>,
    table_name: &'a str,
    fields: &'a [Assignment],
    columns: Rc<[Ident]>,
}

impl<'a, T: 'static + Debug> Update<'a, T> {
    pub fn new(
        storage: &'a dyn Store<T>,
        table_name: &'a str,
        fields: &'a [Assignment],
        columns: Rc<[Ident]>,
    ) -> Result<Self> {
        for assignment in fields.iter() {
            let Assignment { id, .. } = assignment;

            if columns.iter().all(|column| column.value != id.value) {
                return Err(UpdateError::ColumnNotFound(id.value.to_string()).into());
            }
        }

        Ok(Self {
            storage,
            table_name,
            fields,
            columns,
        })
    }

    async fn find(&self, row: &Row, column: &Ident) -> Result<Option<Value>> {
        let context =
            FilterContext::new(self.table_name, Rc::clone(&self.columns), Some(row), None);
        let context = Some(Rc::new(context));

        match self
            .fields
            .iter()
            .find(|assignment| assignment.id.value == column.value)
        {
            None => Ok(None),
            Some(assignment) => {
                let Assignment { id, value } = &assignment;

                let index = self
                    .columns
                    .iter()
                    .position(|column| column.value == id.value)
                    .ok_or(UpdateError::Unreachable)?;

                let evaluated = evaluate(self.storage, context, None, value, false).await?;

                let Row(values) = &row;
                let value = &values[index];

                match evaluated {
                    Evaluated::LiteralRef(v) => value.clone_by(v),
                    Evaluated::Literal(v) => value.clone_by(&v),
                    Evaluated::StringRef(v) => Ok(Value::Str(v.to_string())),
                    Evaluated::ValueRef(v) => Ok(v.clone()),
                    Evaluated::Value(v) => Ok(v),
                }
                .map(Some)
            }
        }
    }

    pub async fn apply(&self, row: Row) -> Result<Row> {
        let Row(values) = &row;

        let values = values.clone().into_iter().enumerate().map(|(i, value)| {
            self.columns
                .get(i)
                .map(|column| (column, value))
                .ok_or_else(|| UpdateError::ConflictOnSchema.into())
        });

        stream::iter(values)
            .and_then(|(column, value)| {
                let row = &row;

                async move {
                    self.find(row, column)
                        .await
                        .transpose()
                        .unwrap_or(Ok(value))
                }
            })
            .try_collect::<Vec<_>>()
            .await
            .map(Row)
    }
}
