/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

package org.apache.cassandra.service.snapshot;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.Predicate;

import javax.management.openmbean.TabularData;
import javax.management.openmbean.TabularDataSupport;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.cassandra.db.ColumnFamilyStore;

public class ListSnapshotsTask implements Callable<Map<String, TabularData>>
{
    private static final Logger logger = LoggerFactory.getLogger(ListSnapshotsTask.class);

    private final SnapshotManager snapshotManager;
    private final Predicate<TableSnapshot> predicate;
    private final Map<String, String> options;
    private final boolean shouldRemoveIfNotExists;

    public ListSnapshotsTask(SnapshotManager snapshotManager,
                             Map<String, String> options,
                             boolean shouldRemoveIfNotExists)
    {
        this.snapshotManager = snapshotManager;
        this.options = options;
        this.predicate = getListingSnapshotsPredicate(options);
        this.shouldRemoveIfNotExists = shouldRemoveIfNotExists;
    }

    private static Predicate<TableSnapshot> getListingSnapshotsPredicate(Map<String, String> options)
    {
        boolean skipExpiring = options != null && Boolean.parseBoolean(options.getOrDefault("no_ttl", "false"));
        boolean includeEphemeral = options != null && Boolean.parseBoolean(options.getOrDefault("include_ephemeral", "false"));
        String selectedKeyspace = options != null ? options.get("keyspace") : null;
        String selectedTable = options != null ? options.get("table") : null;
        String selectedSnapshotName = options != null ? options.get("snapshot") : null;

        return s -> {
            if (selectedSnapshotName != null && !s.getTag().equals(selectedSnapshotName))
                return false;

            if (skipExpiring && s.isExpiring())
                return false;

            if (!includeEphemeral && s.isEphemeral())
                return false;

            if (selectedKeyspace != null && !s.getKeyspaceName().equals(selectedKeyspace))
                return false;

            return selectedTable == null || s.getTableName().equals(selectedTable);
        };
    }

    @Override
    public Map<String, TabularData> call()
    {
        List<TableSnapshot> filteredSnapshots;

        try
        {
            filteredSnapshots = new GetSnapshotsTask(snapshotManager, predicate, shouldRemoveIfNotExists).call();
        }
        catch (Exception e)
        {
            logger.trace("Unable to get snapshots for listing purposes", e);
            return Map.of();
        }

        Map<String, TabularData> snapshotMap = new HashMap<>();
        Set<String> tags = new HashSet<>();

        for (TableSnapshot t : filteredSnapshots)
            tags.add(t.getTag());

        for (String tag : tags)
            snapshotMap.put(tag, new TabularDataSupport(SnapshotDetailsTabularData.TABULAR_TYPE));

        Map<String, Set<String>> keyspaceTables = new HashMap<>();
        for (TableSnapshot s : filteredSnapshots)
        {
            keyspaceTables.computeIfAbsent(s.getKeyspaceName(), ignore -> new HashSet<>());
            keyspaceTables.get(s.getKeyspaceName()).add(s.getTableName());
        }

        Map<String, Set<String>> cfsFiles = new HashMap<>();

        for (Map.Entry<String, Set<String>> entry : keyspaceTables.entrySet())
        {
            for (String table : entry.getValue())
            {
                try
                {
                    ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(entry.getKey(), table);
                    if (cfs == null)
                        continue;

                    cfsFiles.put(cfs.getKeyspaceName() + '.' + cfs.name, cfs.getFilesOfCfs());
                }
                catch (Throwable t)
                {
                    logger.debug("Unable to get all files of live SSTables for {}.{}", entry.getKey(), entry.getValue());
                }
            }
        }

        for (TableSnapshot snapshot : filteredSnapshots)
        {
            TabularDataSupport data = (TabularDataSupport) snapshotMap.get(snapshot.getTag());
            SnapshotDetailsTabularData.from(snapshot, data, cfsFiles.get(snapshot.getKeyspaceTable()));
        }

        return snapshotMap;
    }

    @Override
    public String toString()
    {
        return "ListSnapshotsTask{" +
               "options=" + options +
               '}';
    }
}