import abc
import json
import typing

import boto3


class SessionRepository(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def store_session(self, session_id: str, session_data: typing.Dict):
        pass

    @abc.abstractmethod
    def get_session(self, session_id: str):
        pass


class MockedRepository(SessionRepository):
    def store_session(self, session_id: str, session_data: typing.Dict):
        pass

    def get_session(self, session_id: str):
        return {}


class DynamoDBSessionRepository(SessionRepository):
    def __init__(self, table: str, dynamodb_endpoint: str):
        self._client_lazy = None
        self._session_table = table
        self._dynamodb_endpoint = dynamodb_endpoint

    @property
    def _client(self):
        if not self._client_lazy:
            self._client_lazy = boto3.client(
                'dynamodb',
                # aws_access_key_id=settings.AWS_ACCESS_KEY,
                # aws_secret_access_key=settings.AWS_SECRET_KEY,
                endpoint_url="http://{}".format(self._dynamodb_endpoint) if self._dynamodb_endpoint else None
            )
        return self._client_lazy

    def _obj_to_item(self, raw):
        if isinstance(raw, dict):
            return {
                'M': {
                    k: self._obj_to_item(v)
                    for k, v in raw.items()
                }
            }
        elif isinstance(raw, list):
            return {
                'L': [self._obj_to_item(v) for v in raw]
            }
        elif isinstance(raw, str):
            return {'S': raw}
        elif isinstance(raw, bool):
            return {'BOOL': raw}
        elif isinstance(raw, int):
            return {'N': str(raw)}

    def _item_to_obj(self, item, key=None):
        if key is None or key == 'M':
            ret = {
                k: v for k, v in (self._item_to_obj(v2, k2) for k2, v2 in item.items())
            }
        elif key == 'L':
            ret = [self._item_to_obj(list(k.values())[0], list(k.keys())[0]) for k in item]
        elif key == 'S':
            ret = item
        elif key == 'BOOL':
            ret = bool(item)
        elif key == 'N':
            ret = int(item)
        else:
            dict_items = list(item.items())
            ret = (key, self._item_to_obj(dict_items[0][1], dict_items[0][0]))
        return ret

    def store_session(self, session_id: str, session_data: typing.Dict):
        self._client.put_item(
            TableName=self._session_table,
            Item={
                'session_id': self._obj_to_item(session_id),
                'data': {'S': json.dumps(session_data)}
            }
        )

    def get_session(self, session_id: str):
        item = self._client.get_item(
            TableName=self._session_table,
            Key={
                'session_id': self._obj_to_item(session_id)
            }
        )
        if item and 'Item' in item:
            item = item['Item']
        else:
            return None
        return json.loads(item['data']['S'])
