// Copyright 2018-2021 Cargill Incorporated
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Provides the "list nodes" operation for the `DieselRegistry`.

use diesel::prelude::*;

use crate::error::InvalidStateError;
use crate::registry::{
    diesel::{
        models::{NodeEndpointsModel, NodeKeysModel, NodeMetadataModel, NodesModel},
        schema::{
            splinter_nodes, splinter_nodes_endpoints, splinter_nodes_keys, splinter_nodes_metadata,
        },
    },
    MetadataPredicate, Node, NodeBuilder, RegistryError,
};

use super::{apply_predicate_filters, RegistryOperations};

pub(in crate::registry::diesel) trait RegistryListNodesOperation {
    fn list_nodes(&self, predicates: &[MetadataPredicate]) -> Result<Vec<Node>, RegistryError>;
}

impl<'a, C> RegistryListNodesOperation for RegistryOperations<'a, C>
where
    C: diesel::Connection,
    String: diesel::deserialize::FromSql<diesel::sql_types::Text, C::Backend>,
{
    fn list_nodes(&self, predicates: &[MetadataPredicate]) -> Result<Vec<Node>, RegistryError> {
        self.conn.transaction::<_, _, _>(|| {
            let nodes: Vec<NodesModel> = if predicates.is_empty() {
                // No predicates were specified, just get all nodes
                splinter_nodes::table.load(self.conn)?
            } else {
                // With predicates
                let mut query = splinter_nodes::table
                    .into_boxed()
                    .select(splinter_nodes::all_columns);

                query = apply_predicate_filters(query, predicates);
                query.load(self.conn)?
            };

            // Checking if there are any nodes here serves two purposes: 1) It saves time by
            // skipping the extra queries if they're not needed, and 2) it avoids a potential error
            // caused by an empty list in the `IN` SQL statement generated by `eq_any`.
            if nodes.is_empty() {
                Ok(vec![])
            } else {
                // Get data from secondary tables for all nodes that will be returned, and group
                // them by node
                let identities = nodes.iter().map(|node| &node.identity).collect::<Vec<_>>();

                let endpoints = splinter_nodes_endpoints::table
                    .filter(splinter_nodes_endpoints::identity.eq_any(&identities))
                    .load::<NodeEndpointsModel>(self.conn)?
                    .grouped_by(&nodes);
                let keys = splinter_nodes_keys::table
                    .filter(splinter_nodes_keys::identity.eq_any(&identities))
                    .load::<NodeKeysModel>(self.conn)?
                    .grouped_by(&nodes);
                let metadata = splinter_nodes_metadata::table
                    .filter(splinter_nodes_metadata::identity.eq_any(identities))
                    .load::<NodeMetadataModel>(self.conn)?
                    .grouped_by(&nodes);

                // Build the `Node`s and return them
                nodes
                    .into_iter()
                    .zip(endpoints.into_iter())
                    .zip(keys.into_iter())
                    .zip(metadata.into_iter())
                    .map(|(((node, endpoints), keys), metadata)| {
                        let endpoints = endpoints
                            .into_iter()
                            .map(|endpoint| endpoint.endpoint)
                            .collect::<Vec<_>>();
                        let keys = keys.into_iter().map(|key| key.key).collect::<Vec<_>>();

                        let mut builder = NodeBuilder::new(node.identity)
                            .with_display_name(node.display_name)
                            .with_endpoints(endpoints)
                            .with_keys(keys);

                        for entry in metadata {
                            builder = builder.with_metadata(entry.key, entry.value);
                        }

                        builder.build().map_err(|err| {
                            RegistryError::InvalidStateError(InvalidStateError::with_message(
                                err.to_string(),
                            ))
                        })
                    })
                    .collect::<Result<Vec<_>, _>>()
            }
        })
    }
}
