use crate::{
    errors::MedRecordResult,
    medrecord::querying::{
        attributes::{
            operation::AttributesTreeOperation, AttributesTreeContext, AttributesTreeOperand,
            MultipleAttributesWithIndexContext, MultipleAttributesWithIndexOperand,
            MultipleAttributesWithIndexOperation, MultipleAttributesWithoutIndexContext,
            MultipleAttributesWithoutIndexOperand, MultipleAttributesWithoutIndexOperation,
            MultipleKind, SingleAttributeWithIndexOperand, SingleAttributeWithoutIndexOperand,
            SingleKindWithIndex, SingleKindWithoutIndex,
        },
        group_by::{GroupOperand, GroupedOperand, Ungroup},
        wrapper::Wrapper,
        BoxedIterator, DeepClone, EvaluateBackward, EvaluateForwardGrouped, GroupedIterator,
        RootOperand,
    },
    MedRecord,
};
use medmodels_utils::traits::ReadWriteOrPanic;

impl<O: RootOperand> GroupedOperand for AttributesTreeOperand<O> {
    type Context = GroupOperand<O>;
}

impl<'a, O: 'a + RootOperand> EvaluateBackward<'a> for GroupOperand<AttributesTreeOperand<O>> {
    type ReturnValue =
        GroupedIterator<'a, <AttributesTreeOperand<O> as EvaluateBackward<'a>>::ReturnValue>;

    fn evaluate_backward(&self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue> {
        let partitions = self.context.evaluate_backward(medrecord)?;

        let attributes: Vec<_> = partitions
            .map(|(key, partition)| {
                let reduced_partition: BoxedIterator<_> =
                    Box::new(O::get_attributes_from_indices(medrecord, partition));

                Ok((key, reduced_partition))
            })
            .collect::<MedRecordResult<_>>()?;

        self.operand
            .evaluate_forward_grouped(medrecord, Box::new(attributes.into_iter()))
    }
}

impl<O: RootOperand> Ungroup for GroupOperand<AttributesTreeOperand<O>> {
    type OutputOperand = AttributesTreeOperand<O>;

    fn ungroup(&self) -> Wrapper<Self::OutputOperand> {
        let operand = Wrapper::<Self::OutputOperand>::new(AttributesTreeContext::GroupByOperand(
            self.deep_clone(),
        ));

        self.operand.push_merge_operation(operand.clone());

        operand
    }
}

impl<O: RootOperand> GroupedOperand for MultipleAttributesWithIndexOperand<O> {
    type Context = GroupOperand<AttributesTreeOperand<O>>;
}

impl<'a, O: 'a + RootOperand> EvaluateBackward<'a>
    for GroupOperand<MultipleAttributesWithIndexOperand<O>>
{
    type ReturnValue = GroupedIterator<
        'a,
        <MultipleAttributesWithIndexOperand<O> as EvaluateBackward<'a>>::ReturnValue,
    >;

    fn evaluate_backward(&self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue> {
        let partitions = self.context.evaluate_backward(medrecord)?;

        let MultipleAttributesWithIndexContext::AttributesTree {
            operand: _,
            ref kind,
        } = self.operand.0.read_or_panic().context
        else {
            unreachable!()
        };

        let attributes: Vec<_> = partitions
            .map(|(key, partition)| {
                let reduced_partition: BoxedIterator<_> = match kind {
                    MultipleKind::Max => {
                        Box::new(AttributesTreeOperation::<O>::get_max(partition)?)
                    }
                    MultipleKind::Min => {
                        Box::new(AttributesTreeOperation::<O>::get_min(partition)?)
                    }
                    MultipleKind::Count => {
                        Box::new(AttributesTreeOperation::<O>::get_count(partition)?)
                    }
                    MultipleKind::Sum => {
                        Box::new(AttributesTreeOperation::<O>::get_sum(partition)?)
                    }
                    MultipleKind::Random => {
                        Box::new(AttributesTreeOperation::<O>::get_random(partition)?)
                    }
                };

                Ok((key, reduced_partition))
            })
            .collect::<MedRecordResult<_>>()?;

        self.operand
            .evaluate_forward_grouped(medrecord, Box::new(attributes.into_iter()))
    }
}

impl<O: RootOperand> Ungroup for GroupOperand<MultipleAttributesWithIndexOperand<O>> {
    type OutputOperand = MultipleAttributesWithIndexOperand<O>;

    fn ungroup(&self) -> Wrapper<Self::OutputOperand> {
        let operand = Wrapper::<Self::OutputOperand>::new(
            MultipleAttributesWithIndexContext::MultipleAttributesWithIndexGroupByOperand(
                self.deep_clone(),
            ),
        );

        self.operand.push_merge_operation(operand.clone());

        operand
    }
}

impl<O: RootOperand> GroupedOperand for SingleAttributeWithIndexOperand<O> {
    type Context = GroupOperand<MultipleAttributesWithIndexOperand<O>>;
}

impl<'a, O: 'a + RootOperand> EvaluateBackward<'a>
    for GroupOperand<SingleAttributeWithIndexOperand<O>>
{
    type ReturnValue = GroupedIterator<
        'a,
        <SingleAttributeWithIndexOperand<O> as EvaluateBackward<'a>>::ReturnValue,
    >;

    fn evaluate_backward(&self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue> {
        let partitiions = self.context.evaluate_backward(medrecord)?;

        let attributes: Vec<_> = partitiions
            .map(|(key, partition)| {
                let reduced_partition = match self.operand.0.read_or_panic().kind {
                    SingleKindWithIndex::Max => {
                        MultipleAttributesWithIndexOperation::<O>::get_max(partition)?
                    }
                    SingleKindWithIndex::Min => {
                        MultipleAttributesWithIndexOperation::<O>::get_min(partition)?
                    }
                    SingleKindWithIndex::Random => {
                        MultipleAttributesWithIndexOperation::<O>::get_random(partition)
                    }
                };

                Ok((key, reduced_partition))
            })
            .collect::<MedRecordResult<_>>()?;

        self.operand
            .evaluate_forward_grouped(medrecord, Box::new(attributes.into_iter()))
    }
}

impl<O: RootOperand> Ungroup for GroupOperand<SingleAttributeWithIndexOperand<O>> {
    type OutputOperand = MultipleAttributesWithIndexOperand<O>;

    fn ungroup(&self) -> Wrapper<Self::OutputOperand> {
        let operand = Wrapper::<Self::OutputOperand>::new(
            MultipleAttributesWithIndexContext::SingleAttributeWithIndexGroupByOperand(
                self.deep_clone(),
            ),
        );

        self.operand.push_merge_operation(operand.clone());

        operand
    }
}

impl<O: RootOperand> GroupedOperand for SingleAttributeWithoutIndexOperand<O> {
    type Context = GroupOperand<MultipleAttributesWithIndexOperand<O>>;
}

impl<'a, O: 'a + RootOperand> EvaluateBackward<'a>
    for GroupOperand<SingleAttributeWithoutIndexOperand<O>>
{
    type ReturnValue = GroupedIterator<
        'a,
        <SingleAttributeWithoutIndexOperand<O> as EvaluateBackward<'a>>::ReturnValue,
    >;

    fn evaluate_backward(&self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue> {
        let partitiions = self.context.evaluate_backward(medrecord)?;

        let attributes: Vec<_> = partitiions
            .map(|(key, partition)| {
                let partition = partition.map(|(_, attribute)| attribute);

                let reduced_partition = match self.operand.0.read_or_panic().kind {
                    SingleKindWithoutIndex::Max => {
                        MultipleAttributesWithoutIndexOperation::<O>::get_max(partition)?
                    }
                    SingleKindWithoutIndex::Min => {
                        MultipleAttributesWithoutIndexOperation::<O>::get_min(partition)?
                    }
                    SingleKindWithoutIndex::Count => Some(
                        MultipleAttributesWithoutIndexOperation::<O>::get_count(partition),
                    ),
                    SingleKindWithoutIndex::Sum => {
                        MultipleAttributesWithoutIndexOperation::<O>::get_sum(partition)?
                    }
                    SingleKindWithoutIndex::Random => {
                        MultipleAttributesWithoutIndexOperation::<O>::get_random(partition)
                    }
                };

                Ok((key, reduced_partition))
            })
            .collect::<MedRecordResult<_>>()?;

        self.operand
            .evaluate_forward_grouped(medrecord, Box::new(attributes.into_iter()))
    }
}

impl<O: RootOperand> Ungroup for GroupOperand<SingleAttributeWithoutIndexOperand<O>> {
    type OutputOperand = MultipleAttributesWithoutIndexOperand<O>;

    fn ungroup(&self) -> Wrapper<Self::OutputOperand> {
        let operand = Wrapper::<Self::OutputOperand>::new(
            MultipleAttributesWithoutIndexContext::GroupByOperand(self.deep_clone()),
        );

        self.operand.push_merge_operation(operand.clone());

        operand
    }
}
