"""Cryptocurrency Discovery Controller"""
__docformat__ = "numpy"

# pylint: disable=R0904, C0302, W0622, C0201
import argparse
import logging
from typing import List, Optional

from openbb_terminal.core.session.current_user import get_current_user
from openbb_terminal.cryptocurrency.discovery import (
    coinmarketcap_model,
    coinmarketcap_view,
    coinpaprika_model,
    coinpaprika_view,
    dappradar_model,
    dappradar_view,
    pycoingecko_model,
    pycoingecko_view,
)
from openbb_terminal.custom_prompt_toolkit import NestedCompleter
from openbb_terminal.decorators import log_start_end
from openbb_terminal.helper_funcs import EXPORT_ONLY_RAW_DATA_ALLOWED, check_positive
from openbb_terminal.menu import session
from openbb_terminal.parent_classes import BaseController
from openbb_terminal.rich_config import MenuText, console, get_ordered_list_sources
from openbb_terminal.stocks import stocks_helper

logger = logging.getLogger(__name__)


class DiscoveryController(BaseController):
    """Discovery Controller class"""

    CHOICES_COMMANDS = [
        "search",
        "top",
        "trending",
        "gainers",
        "losers",
        "nft_mktp_chains",
        "nft_mktp",
        "dapps",
        "dapp_categories",
        "dapp_chains",
        "dapp_metrics",
        "defi_chains",
        "tokens",
    ]

    PATH = "/crypto/disc/"
    CHOICES_GENERATION = True

    def __init__(self, queue: Optional[List[str]] = None):
        """Constructor"""
        super().__init__(queue)

        if session and get_current_user().preferences.USE_PROMPT_TOOLKIT:
            choices: dict = self.choices_default

            ordered_list_sources_top = get_ordered_list_sources(f"{self.PATH}top")
            if ordered_list_sources_top and ordered_list_sources_top[0] == "CoinGecko":
                choices["top"]["--sort"] = {
                    c: {}
                    for c in stocks_helper.format_parse_choices(
                        pycoingecko_view.COINS_COLUMNS
                    )
                }
            else:
                choices["top"]["--sort"] = {
                    c: {}
                    for c in stocks_helper.format_parse_choices(
                        coinmarketcap_model.FILTERS
                    )
                }

            choices["top"]["-s"] = choices["top"]["--sort"]

            self.completer = NestedCompleter.from_nested_dict(choices)

    def print_help(self):
        """Print help"""
        mt = MenuText("crypto/disc/")
        mt.add_cmd("top")
        mt.add_cmd("trending")
        mt.add_cmd("gainers")
        mt.add_cmd("losers")
        mt.add_cmd("search")
        mt.add_cmd("nft_mktp_chains")
        mt.add_cmd("nft_mktp")
        mt.add_cmd("dapps")
        mt.add_cmd("dapp_categories")
        mt.add_cmd("dapp_chains")
        mt.add_cmd("dapp_metrics")
        mt.add_cmd("defi_chains")
        mt.add_cmd("tokens")
        console.print(text=mt.menu_text, menu="Cryptocurrency - Discovery")

    @log_start_end(log=logger)
    def call_top(self, other_args):
        """Process top command"""
        ordered_list_sources_top = get_ordered_list_sources(f"{self.PATH}top")

        if ordered_list_sources_top and ordered_list_sources_top[0] == "CoinGecko":
            argument_sort_default = "Market Cap Rank"
        else:
            argument_sort_default = "CMC_Rank"

        parser = argparse.ArgumentParser(
            prog="top",
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            description="""Display N coins from the data source, if the data source is CoinGecko it
            can receive a category as argument (-c decentralized-finance-defi or -c stablecoins)
            and will show only the top coins in that category.
            can also receive sort arguments (these depend on the source), e.g., --sort Volume [$]
            You can sort by {Symbol,Name,Price [$],Market Cap,Market Cap Rank,Volume [$]} with CoinGecko
            Number of coins to show: -l 10
            """,
        )

        parser.add_argument(
            "-c",
            "--category",
            default="",
            dest="category",
            help="Category (e.g., stablecoins). Empty for no category. Only works for 'CoinGecko' source.",
            choices=pycoingecko_model.get_categories_keys(),
            metavar="CATEGORY",
        )

        parser.add_argument(
            "-l",
            "--limit",
            default=10,
            dest="limit",
            help="Limit of records",
            type=check_positive,
        )
        parser.add_argument(
            "-s",
            "--sort",
            dest="sortby",
            nargs="+",
            help="Sort by given column. Default: Market Cap Rank",
            default=stocks_helper.format_parse_choices([argument_sort_default]),
            metavar="SORTBY",
        )

        parser.add_argument(
            "-r",
            "--reverse",
            action="store_true",
            dest="reverse",
            default=False,
            help=(
                "Data is sorted in descending order by default. "
                "Reverse flag will sort it in an ascending way. "
                "Only works when raw data is displayed."
            ),
        )
        if other_args and other_args[0][0] != "-":
            other_args.insert(0, "-c")

        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
        )
        if ns_parser:
            if ns_parser.source == "CoinGecko":
                pycoingecko_view.display_coins(
                    sortby=" ".join(ns_parser.sortby),
                    category=ns_parser.category,
                    limit=ns_parser.limit,
                    export=ns_parser.export,
                    sheet_name=" ".join(ns_parser.sheet_name)
                    if ns_parser.sheet_name
                    else None,
                    ascend=ns_parser.reverse,
                )
            elif ns_parser.source == "CoinMarketCap":
                coinmarketcap_view.display_cmc_top_coins(
                    limit=ns_parser.limit,
                    sortby=ns_parser.sortby,
                    ascend=ns_parser.reverse,
                    export=ns_parser.export,
                    sheet_name=" ".join(ns_parser.sheet_name)
                    if ns_parser.sheet_name
                    else None,
                )

    @log_start_end(log=logger)
    def call_dapps(self, other_args):
        """Process dapps command"""
        parser = argparse.ArgumentParser(
            prog="dapps",
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            description="""
            Shows available decentralized applications [Source: https://dappradar.com/]
            Accepts --chain argument to filter by blockchain
                    --page argument to show a specific page. Default: 1
                    --limit argument to limit the number of records per page. Default: 15
            """,
        )
        parser.add_argument(
            "-c",
            "--chain",
            dest="chain",
            help="Filter by blockchain",
            metavar="CHAIN",
        )
        parser.add_argument(
            "-p",
            "--page",
            dest="page",
            type=check_positive,
            help="Page number",
            default=1,
        )
        parser.add_argument(
            "-l",
            "--limit",
            dest="limit",
            type=check_positive,
            help="Number of records to display per page",
            default=15,
        )

        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
        )
        if ns_parser:
            dappradar_view.display_dapps(
                chain=ns_parser.chain,
                page=ns_parser.page,
                resultPerPage=ns_parser.limit,
                export=ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
            )

    @log_start_end(log=logger)
    def call_dapp_categories(self, other_args):
        """Process dapp_categories command"""
        parser = argparse.ArgumentParser(
            prog="dapp_categories",
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            description="""
            Shows available dapp categories [Source: https://dappradar.com/]
            """,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
        )
        if ns_parser:
            dappradar_view.display_dapp_categories(
                export=ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
            )

    @log_start_end(log=logger)
    def call_dapp_chains(self, other_args):
        """Process dapp_chains command"""
        parser = argparse.ArgumentParser(
            prog="dapp_chains",
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            description="""
            Shows available dapp chains [Source: https://dappradar.com/]
            """,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
        )
        if ns_parser:
            dappradar_view.display_dapp_chains(
                export=ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
            )

    @log_start_end(log=logger)
    def call_dapp_metrics(self, other_args):
        """Process dapp_metrics command"""
        parser = argparse.ArgumentParser(
            prog="dapp_metrics",
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            description="""
            Shows dapp metrics [Source: https://dappradar.com/]
            Accepts --dappId argument to specify the dapp
                    --chain argument to filter by blockchain for multi-chain dapps
                    --time_range argument to specify the time range. Default: 7d (can be 24h, 7d, 30d)
            """,
        )
        parser.add_argument(
            "-d",
            "--dappId",
            dest="dappId",
            help="Dapp ID",
            metavar="DAPP_ID",
        )
        parser.add_argument(
            "-c",
            "--chain",
            dest="chain",
            help="Filter by blockchain",
            metavar="CHAIN",
        )
        parser.add_argument(
            "-t",
            "--time_range",
            dest="time_range",
            help="Time range",
            metavar="TIME_RANGE",
            choices=["24h", "7d", "30d"],
            default="7d",
        )

        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
        )
        if ns_parser:
            dappradar_view.display_dapp_metrics(
                dappId=ns_parser.dappId,
                chain=ns_parser.chain,
                time_range=ns_parser.time_range,
                export=ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
            )

    @log_start_end(log=logger)
    def call_gainers(self, other_args):
        """Process gainers command"""
        parser = argparse.ArgumentParser(
            prog="gainers",
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            description="""
            Shows Largest Gainers - coins which gain the most in given period.
            You can use parameter --interval to set which timeframe are you interested in: {14d,1h,1y,200d,24h,30d,7d}
            You can look on only N number of records with --limit,
            You can sort by {Symbol,Name,Price [$],Market Cap,Market Cap Rank,Volume [$]} with --sort.
            """,
        )

        parser.add_argument(
            "-i",
            "--interval",
            dest="interval",
            type=str,
            help="time period, one from {14d,1h,1y,200d,24h,30d,7d}",
            default="1h",
            choices=pycoingecko_model.API_PERIODS,
        )

        parser.add_argument(
            "-l",
            "--limit",
            dest="limit",
            type=check_positive,
            help="Number of records to display",
            default=15,
        )

        parser.add_argument(
            "-s",
            "--sort",
            dest="sortby",
            nargs="+",
            help="Sort by given column. Default: Market Cap Rank",
            default=["market_cap"],
            choices=stocks_helper.format_parse_choices(
                pycoingecko_model.GAINERS_LOSERS_COLUMNS
            ),
            metavar="SORTBY",
        )
        parser.add_argument(
            "-r",
            "--reverse",
            action="store_true",
            dest="reverse",
            default=False,
            help=(
                "Data is sorted in descending order by default. "
                "Reverse flag will sort it in an ascending way. "
                "Only works when raw data is displayed."
            ),
        )

        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
        )
        if ns_parser:
            pycoingecko_view.display_gainers(
                interval=ns_parser.interval,
                limit=ns_parser.limit,
                export=ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
                sortby=" ".join(ns_parser.sortby),
                ascend=ns_parser.reverse,
            )

    @log_start_end(log=logger)
    def call_losers(self, other_args):
        """Process losers command"""
        parser = argparse.ArgumentParser(
            prog="losers",
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            description="""
           Shows Largest Losers - coins which price dropped the most in given period
           You can use parameter --interval to set which timeframe are you interested in: {14d,1h,1y,200d,24h,30d,7d}
           You can look on only N number of records with --limit,
           You can sort by {Symbol,Name,Price [$],Market Cap,Market Cap Rank,Volume [$]} with --sort.
            """,
        )

        parser.add_argument(
            "-i",
            "--interval",
            dest="interval",
            type=str,
            help="time period, one from {14d,1h,1y,200d,24h,30d,7d}",
            default="1h",
            choices=pycoingecko_model.API_PERIODS,
        )
        parser.add_argument(
            "-l",
            "--limit",
            dest="limit",
            type=check_positive,
            help="Number of records to display",
            default=15,
        )
        parser.add_argument(
            "-s",
            "--sort",
            dest="sortby",
            nargs="+",
            help="Sort by given column. Default: Market Cap Rank",
            default=["Market Cap"],
            choices=stocks_helper.format_parse_choices(
                pycoingecko_model.GAINERS_LOSERS_COLUMNS
            ),
            metavar="SORTBY",
        )
        parser.add_argument(
            "-r",
            "--reverse",
            action="store_true",
            dest="reverse",
            default=False,
            help=(
                "Data is sorted in descending order by default. "
                "Reverse flag will sort it in an ascending way. "
                "Only works when raw data is displayed."
            ),
        )
        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
        )
        if ns_parser:
            pycoingecko_view.display_losers(
                interval=ns_parser.interval,
                limit=ns_parser.limit,
                export=ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
                sortby=" ".join(ns_parser.sortby),
                ascend=ns_parser.reverse,
            )

    @log_start_end(log=logger)
    def call_trending(self, other_args):
        """Process trending command"""
        parser = argparse.ArgumentParser(
            prog="trending",
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            description="""Discover trending coins (Top-7) on CoinGecko in the last 24 hours
            """,
        )

        ns_parser = self.parse_known_args_and_warn(
            parser,
            other_args,
            EXPORT_ONLY_RAW_DATA_ALLOWED,
        )
        if ns_parser:
            pycoingecko_view.display_trending(
                export=ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
            )

    @log_start_end(log=logger)
    def call_search(self, other_args):
        """Process search command"""
        parser = argparse.ArgumentParser(
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            prog="search",
            description="""Search over CoinPaprika API
            You can display only N number of results with --limit parameter.
            You can sort data by id, name , category --sort parameter and also with --reverse flag to sort descending.
            To choose category in which you are searching for use --cat/-c parameter. Available categories:
            currencies|exchanges|icos|people|tags|all
            Displays:
                id, name, category""",
        )
        parser.add_argument(
            "-q",
            "--query",
            help="phrase for search",
            dest="query",
            nargs="+",
            type=str,
            required="-h" not in other_args,
        )
        parser.add_argument(
            "-c",
            "--category",
            help="Categories to search: currencies|exchanges|icos|people|tags|all. Default: all",
            dest="category",
            default="all",
            type=str,
            choices=coinpaprika_model.CATEGORIES,
        )
        parser.add_argument(
            "-l",
            "--limit",
            default=10,
            dest="limit",
            help="Limit of records",
            type=check_positive,
        )
        parser.add_argument(
            "-s",
            "--sort",
            dest="sortby",
            type=str,
            help="Sort by given column. Default: id",
            default="id",
            choices=coinpaprika_model.FILTERS,
        )
        parser.add_argument(
            "-r",
            "--reverse",
            action="store_true",
            dest="reverse",
            default=False,
            help=(
                "Data is sorted in descending order by default. "
                "Reverse flag will sort it in an ascending way. "
                "Only works when raw data is displayed."
            ),
        )
        if other_args and other_args[0][0] != "-":
            other_args.insert(0, "-q")

        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
        )
        if ns_parser:
            coinpaprika_view.display_search_results(
                limit=ns_parser.limit,
                sortby=ns_parser.sortby,
                ascend=ns_parser.reverse,
                export=ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
                query=" ".join(ns_parser.query),
                category=ns_parser.category,
            )

    @log_start_end(log=logger)
    def call_nft_mktp_chains(self, other_args):
        """Process nft_mktp_chains command"""
        parser = argparse.ArgumentParser(
            prog="nft_mktp_chains",
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            description="""
            Shows NFT marketplace chains [Source: https://dappradar.com/]
            """,
        )

        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
        )

        if ns_parser:
            dappradar_view.display_nft_marketplace_chains(
                export=ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
            )

    @log_start_end(log=logger)
    def call_defi_chains(self, other_args):
        """Process defi_chains command"""
        parser = argparse.ArgumentParser(
            prog="defi_chains",
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            description="""
            Shows DeFi chains [Source: https://dappradar.com/]
            """,
        )

        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
        )

        if ns_parser:
            dappradar_view.display_defi_chains(
                export=ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
            )

    @log_start_end(log=logger)
    def call_nft_mktp(self, other_args):
        """Process nft_mktp command"""
        parser = argparse.ArgumentParser(
            prog="nft_mktp",
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            description="""
            Shows NFT marketplaces [Source: https://dappradar.com/]
            Accepts --chain  to filter by blockchain
                    --sortby {name, avgPrice, volume, traders...} to sort by column
                    --order {asc, desc} to sort ascending or descending
                    --limit to limit number of records
            """,
        )
        parser.add_argument(
            "-c",
            "--chain",
            dest="chain",
            help="Name of the blockchain to filter by.",
        )
        parser.add_argument(
            "-s",
            "--sortby",
            dest="sortby",
            nargs="+",
            help="Sort by given column.",
            choices=stocks_helper.format_parse_choices(dappradar_model.NFT_COLUMNS),
            metavar="SORTBY",
        )
        parser.add_argument(
            "-o",
            "--order",
            dest="order",
            help="Order of sorting. Default: desc",
            default="desc",
        )
        parser.add_argument(
            "-l",
            "--limit",
            dest="limit",
            type=check_positive,
            help="Number of records to display",
            default=10,
        )
        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
        )
        if ns_parser:
            dappradar_view.display_nft_marketplaces(
                sortby=ns_parser.sortby,
                order=ns_parser.order,
                chain=ns_parser.chain,
                limit=ns_parser.limit,
                export=ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
            )

    @log_start_end(log=logger)
    def call_tokens(self, other_args):
        """Process tokens command"""
        parser = argparse.ArgumentParser(
            prog="tokens",
            add_help=False,
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            description="""
            Shows chains that support tokens [Source: https://dappradar.com/]
            """,
        )

        ns_parser = self.parse_known_args_and_warn(
            parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
        )
        if ns_parser:
            dappradar_view.display_token_chains(
                export=ns_parser.export,
                sheet_name=" ".join(ns_parser.sheet_name)
                if ns_parser.sheet_name
                else None,
            )
