"""Cryptopanic model"""
__docformat__ = "numpy"

import logging
import math
import textwrap
import time
from typing import Any, Optional

import pandas as pd
import requests

import openbb_terminal.config_terminal as cfg
from openbb_terminal.rich_config import console
from openbb_terminal.decorators import check_api_key, log_start_end
from openbb_terminal.cryptocurrency.cryptocurrency_helpers import prepare_all_coins_df
from openbb_terminal.parent_classes import CRYPTO_SOURCES

logger = logging.getLogger(__name__)

CATEGORIES = ["news", "media"]

SORT_FILTERS = [
    "published_at",
    "domain",
    "title",
    "negative_votes",
    "positive_votes",
]

FILTERS = [
    "rising",
    "hot",
    "bullish",
    "bearish",
    "important",
    "saved",
    "lol",
]

REGIONS = ["en", "de", "es", "fr", "nl", "it", "pt", "ru"]


class ApiKeyException(Exception):
    """Api Key Exception object"""

    @log_start_end(log=logger)
    def __init__(self, message: str):
        super().__init__(message)
        self.message = message

    @log_start_end(log=logger)
    def __str__(self) -> str:
        return f"ApiKeyException: {self.message}"


@log_start_end(log=logger)
@check_api_key(["API_CRYPTO_PANIC_KEY"])
def make_request(**kwargs: Any) -> Optional[dict]:
    """Helper methods for requests [Source: https://cryptopanic.com/developers/api/]

    Parameters
    ----------
    kwargs: Any
        Keyword arguments with parameters for GET request to cryptopanic api.

    Returns
    -------
    Optional[dict]
        response from api request
    """

    api_key = cfg.API_CRYPTO_PANIC_KEY
    crypto_panic_url = "https://cryptopanic.com/api/v1"

    post_kind = kwargs.get("post_kind")
    filter_ = kwargs.get("filter_")
    region = kwargs.get("region")
    currency = kwargs.get("currency")
    source = kwargs.get("source")

    if post_kind not in ["news", "media", "all"]:
        post_kind = "all"

    url = f"{crypto_panic_url}/posts/?auth_token={api_key}" + f"&kind={post_kind}"

    if filter_ and filter_ in [
        "rising",
        "hot",
        "bullish",
        "bearish",
        "important",
        "saved",
        "lol",
    ]:
        url += f"&filter={filter_}"

    if region and region in REGIONS:
        url += f"&regions={region}"

    if currency and source:
        source_full = CRYPTO_SOURCES[source]
        df_mapp = prepare_all_coins_df()

        try:
            mapped_coin = df_mapp.loc[df_mapp[source_full] == currency][
                "Symbol"
            ].values[0]
            url += f"&currency={mapped_coin.upper()}"
        except IndexError:
            console.print(f"Cannot find news for {currency}.\n")
            return {}

    response = requests.get(url)
    response_json = response.json()
    result = None

    if response.status_code == 200:
        result = response_json
    else:
        if "Token not found" in response_json["info"]:
            console.print("[red]Invalid API Key[/red]\n")
        else:
            console.print(response_json["info"])

        logger.warning("Invalid authentication: %s", response.text)

    return result


@log_start_end(log=logger)
def _parse_post(post: dict) -> dict:
    """Helper method - parse news response object to dictionary with target structure.
    [Source: https://cryptopanic.com/]

    Parameters
    ----------
    post: dict
        Response object from cryptopanic api.

    Returns
    -------
    dict
        Parsed dictionary with target data structure.

    """
    return {
        "published_at": post.get("published_at"),
        "domain": post.get("domain"),
        "title": post.get("title"),
        "negative_votes": post["votes"].get("negative"),
        "positive_votes": post["votes"].get("positive"),
        "link": post["url"],
    }


@log_start_end(log=logger)
def get_news(
    limit: int = 60,
    post_kind: str = "news",
    filter_: Optional[str] = None,
    region: str = "en",
    source: Optional[str] = None,
    symbol: Optional[str] = None,
    sortby: str = "published_at",
    ascend: bool = True,
) -> pd.DataFrame:
    """Get recent posts from CryptoPanic news aggregator platform. [Source: https://cryptopanic.com/]

    Parameters
    ----------
    limit: int
        number of news to fetch
    post_kind: str
        Filter by category of news. Available values: news or media.
    filter_: Optional[str]
        Filter by kind of news. One from list: rising|hot|bullish|bearish|important|saved|lol
    region: str
        Filter news by regions. Available regions are: en (English), de (Deutsch), nl (Dutch),
        es (Español), fr (Français), it (Italiano), pt (Português), ru (Русский)
    sortby: str
        Key to sort by.
    ascend: bool
        Sort in ascend order.

    Returns
    -------
    pd.DataFrame
        DataFrame with recent news from different sources filtered by provided parameters.
    """

    if post_kind not in CATEGORIES:
        post_kind = "news"

    results = []

    kwargs = {}
    if source:
        kwargs["source"] = source
    response = make_request(
        post_kind=post_kind, filter_=filter_, region=region, currency=symbol, **kwargs
    )

    if response:
        data, next_page, _ = (
            response["results"],
            response.get("next"),
            response.get("count"),
        )

        for post in data:
            results.append(_parse_post(post))

        number_of_pages = math.ceil(limit // 20)
        counter = 0
        while counter < number_of_pages and next_page:
            counter += 1
            try:
                time.sleep(0.2)
                res = requests.get(next_page).json()
                for post in res["results"]:
                    results.append(_parse_post(post))
                next_page = res.get("next")
            except Exception as e:  # noqa: F841
                logger.exception(str(e))
                console.print(
                    "[red]Something went wrong while fetching news from API[/red]\n"
                )
                return pd.DataFrame()

        try:
            df = pd.DataFrame(results)
            df["title"] = df["title"].apply(
                lambda x: "\n".join(textwrap.wrap(x, width=66))
                if isinstance(x, str)
                else x
            )
            df["published_at"] = pd.to_datetime(df["published_at"]).dt.date
            df = df.sort_values(by=sortby, ascending=ascend)
            return df
        except Exception as e:  # noqa: F841
            logger.exception(str(e))
            console.print("[red]Something went wrong with DataFrame creation[/red]\n")
            return pd.DataFrame()
    return pd.DataFrame()
