from collections import OrderedDict
from dataclasses import dataclass
import requests

from sqlitedict import SqliteDict

from news_signals import log


logger = log.create_logger(__name__)


class NotAuthenticatedError(Exception):
    pass


class LoginError(Exception):
    pass


class AylienAPIError(Exception):
    pass


@dataclass
class RadarUser:
    radar_id: str = None
    token: str = None
    refresh_token: str = None
    auth_url: str = 'https://api.aylien.com/v1/oauth/token'
    api_url_prefix: str = 'https://api.aylien.com/v1'

    @property
    def valid_token(self):
        try:
            self.set_token_with_refresh()
            return True
        except LoginError as e:
            return False

    def set_token(self, password):
        try:
            auth_resp = requests.post(
                self.auth_url,
                auth=(self.radar_id, password),
                data={"grant_type": "password"}
            )
            auth_json = auth_resp.json()
            self.token = auth_json["access_token"]
            self.refresh_token = auth_json["refresh_token"]
        except KeyError as e:
            raise LoginError(str(e))
        return self

    def set_token_with_refresh(self):
        try:
            auth_json = requests.post(
                self.auth_url,
                # auth='noauth',
                data={
                    'grant_type': 'refresh_token',
                    'refresh_token': self.refresh_token
                }
            ).json()
            self.token = auth_json["access_token"]
            self.refresh_token = auth_json["refresh_token"]
        except Exception as e:
            raise LoginError(str(e))
        return self

    def reset_token(self):
        self.token = None

    def to_dict(self):
        return {
            'radar_id': self.radar_id,
            'token': self.token,
            'refresh_token': self.refresh_token,
            'auth_url': self.auth_url
        }

    @staticmethod
    def from_dict(data):
        return RadarUser(**data)

    def _headers(self):
        return {
            "Authorization": "Bearer {}".format(self.token),
            # "Cache-Control": "no-cache",
            # "Pragma": "no-cache"
        }

    def matrices(self):
        self.set_token_with_refresh()
        try:
            response = requests.get(
                f'{self.api_url_prefix}/feeds/v1/matrices',
                headers=self._headers(),
            )
            matrices = OrderedDict()
            for m in response.json()['items']:
                matrices[m['id']] = self.matrix(m['id'])
        except Exception as e:
            raise AylienAPIError(f'Error requesting matrices for user: {str(e)}')
        return matrices

    def matrix(self, matrix_id):
        self.set_token_with_refresh()
        response = requests.get(
            f'{self.api_url_prefix}/feeds/v1/matrices/{matrix_id}',
            headers=self._headers()
        ).json()

        return response

    def matrix_anomalies(self, matrix, start_date, end_date):
        """
        we need to build the request payloads and call the anomalies endpoint
        Map RADAR saved feeds matrices to ADP service queries
        """
        self.set_token_with_refresh()

        adp_endpoint = 'https://app.aylien.com/api/fetchAnomalies'
        # TODO: not clear why an id could be empty, which would cause a
        # mismatch between the query generated by Radar UI and the query
        # generated here, leading to the query generated here not being a
        # cache hit, leading to very slow response times.
        entity_ids = [e['id'] for e in matrix['entities'] if len(e['id'])]
        topics = [
            {'label': t['label'], 'query': t['query']} for t in matrix['topics']
        ]
        q = {
            'start_date': start_date,
            'end_date': end_date,
            'entity_ids': entity_ids,
            'topics': topics
        }
        # TODO: cache anomaly matrix API responses
        # q_signature = json.dumps(q)
        # if self.cache():
        #     pass

        # print('Sending Anomaly Query:')
        # print(json.dumps(q, indent=2))
        response = requests.post(
            adp_endpoint,
            json=q,
            headers=self._headers()
        )
        anomalies = response.json()
        return anomalies

    def matrix_cell(self, cell_payload):
        """
        """
        self.set_token_with_refresh()

        cell_endpoint = 'https://api.aylien.com/v1/anomaly-detection/cell'
        response = requests.post(
            cell_endpoint,
            json=cell_payload,
            headers=self._headers()
        )
        cell_data = response.json()
        return cell_data

    def create_matrix(self, matrix_config):
        self.set_token_with_refresh()
        return requests.post(
            'https://api.aylien.com/v1/feeds/v1/matrices',
            json=matrix_config,
            headers=self._headers()
        )


@dataclass
class SlackUser:
    slack_id: str = None
    slack_workspace: str = None

    # foreign key
    # could be one-->many
    radar_user: RadarUser = None

    def to_dict(self):
        return {
            'slack_id': self.slack_id,
            'slack_workspace': self.slack_workspace,
            'radar_user': self.radar_user.to_dict()
        }

    @staticmethod
    def from_dict(data):
        data['radar_user'] = RadarUser.from_dict(data['radar_user'])
        return SlackUser(**data)


class SqliteUserStore:
    """
    this could also be implemented as a Signal ORM,
    would probably be a better design but leaving like this
    until the interface is more solid.
    """
    def __init__(self, db_path):
        self.db_path = db_path
        self.slack_users = SqliteDict(
            db_path,
            tablename='slack_users',
            autocommit=True
        )
        self.radar_users = SqliteDict(
            db_path,
            tablename='radar_users',
            autocommit=True
        )

    def put(self, user: SlackUser):
        self.slack_users[user.slack_id] = user.to_dict()
        self.radar_users[user.radar_user.radar_id] = user.radar_user.to_dict()
        logger.info(f'put {user.radar_user.radar_id} into local radar user store')
        return user.slack_id

    def get(self, slack_id=None, radar_id=None):
        try:
            if slack_id is not None:
                return SlackUser.from_dict(self.slack_users[slack_id])
            else:
                return RadarUser.from_dict(self.radar_users[radar_id])
        except KeyError:
            return None

