# -*- coding: utf-8 -*-
import os
import logging
from ylog.context import log_context
from flask import g
from flask_login import current_user

from datacloud.score_api.validators.validators import Partner
from datacloud.score_api.server.errors import ApiException
from datacloud.score_api.server.blueprints.views import ApiView

from collections import namedtuple
from datacloud.score_api.storage.scores.ydb.storage import YdbScoresMetaStorage


logger = logging.getLogger(__name__)


__all__ = [
    'UnknownPartner',
    'ScoreReadForbidden',
    'ScoreValueView',
]


REQUEST_LOG_TAG = 'score_value_request'
IDS_TO_TAKE = 5


class UnknownPartner(ApiException):
    """ A partner does not exist """
    status_code = 404
    code = 'unknown_partner'


class ScoreReadForbidden(ApiException):
    """ Try to read not enabled score """
    status_code = 403
    code = 'score_read_forbidden'


input_schema = {
    "description": "DataCloud score API query",
    "id": "score_request",
    "properties": {
        "response_ids": {
          "type": "object",
          "properties": {
              "external_id": {
                  "type": "string"
              },
              "request_id": {
                  "type": "string"
              }
          }
        },
        "user_ids": {
            "type": "object",
            "properties": {
                "external_id": {
                    "type": "string"
                },
                "phones": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "id_value": {
                                "type": "string"
                            },
                            "phone": {
                                "type": "string"
                            }
                        }
                    }
                },
                "emails": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "id_value": {
                                "type": "string"
                            },
                            "email": {
                                "type": "string"
                            }
                        }
                    }
                },
                "cookies": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "cookie": {
                                "type": "string"
                            },
                            "cookie_vendor": {
                                "type": "string"
                            }
                        },
                        "required": [
                            "cookie",
                            "cookie_vendor"
                        ]
                    }
                }
            }
        },
        "scores": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "score_name": {
                        "type": "string"
                    }
                },
                "required": ["score_name"]
            }
        }
    },
    "required": [
        "user_ids",
        "scores"
    ]
}


output_schema = {
    "description": "DataCloud score API response",
    "id": "score_response",
    "properties": {
        "response_ids": {
          "type": "object",
          "properties": {
              "external_id": {
                  "type": "string"
              },
              "request_id": {
                  "type": "string"
              }
          }
        },
        "scores": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "score_name": {
                        "type": "string",
                    },
                    "has_score": {
                        "type": "boolean",
                    },
                    "score_value": {
                        "type": "number",
                    }
                },
                "required": ["score_name", "has_score"]
            }
        }
    },
    "required": ["scores"]
}


ScoreValue = namedtuple('ScoreValue', 'name value')


class ScoreValueView(ApiView):
    """
    View for reading score values
    """
    params_schema = {
        'partner_id': Partner,
    }
    request_body_schema = input_schema
    request_body_required = True
    result_schema = output_schema
    permissions = {'partner_read'}

    def __init__(self, ydb_manager, connection_params, ydb_root_dir, score_path_storage, cookie_sync_api, *args, **kwargs):
        """
        Params:
        """
        super(ScoreValueView, self).__init__(**kwargs)
        self._connection_params = connection_params
        self._ydb_manager = ydb_manager
        self._ydb_root_dir = ydb_root_dir
        self._score_path_storage = score_path_storage
        self._ScoreStorageClass = YdbScoresMetaStorage
        self._scores_root_path = os.path.join(self._ydb_root_dir, 'scores')
        self._cookie_sync_api = cookie_sync_api

    def get(self, partner_id):
        return self._process(partner_id)

    def post(self, partner_id):
        return self._process(partner_id)

    def _process(self, partner_id):
        request_body = g.request_body
        with log_context(partner_id=partner_id, query=request_body, user=current_user.id):
            try:
                score_values = self._get_score_values(partner_id, request_body)
            except Exception as e:  # noqa
                with log_context(tag=REQUEST_LOG_TAG, success=False):
                    logger.info('score values request processing fail')
                raise

            response_scores = []
            for score_name, value in score_values:
                r = {
                    'score_name': score_name,
                    'has_score': value is not None,
                }
                if value is not None:
                    r['score_value'] = value
                response_scores.append(r)

            response = {
                'scores': response_scores,
            }
            if 'response_ids' in request_body:
                response['response_ids'] = request_body['response_ids']

            with log_context(tag=REQUEST_LOG_TAG, success=True, response=response):
                logger.info('score values requested by %s', current_user.id)
            return response

    def _get_score_values(self, partner_id, query):
        # YDB can not work with Unicode
        partner_id = str(partner_id)

        requested_partner_score_names = set(s['score_name'] for s in query.get('scores', []))
        scores_list = {score.partner_score_name: score for score in self._score_path_storage.get_partner_scores(partner_id)}
        available_partner_score_names = set(scores_list.keys())
        if requested_partner_score_names - available_partner_score_names:
            raise ScoreReadForbidden()
        user_ids = query.get('user_ids')
        user_info = []
        user_info.extend(user_ids.get('phones', [])[:IDS_TO_TAKE])
        user_info.extend(user_ids.get('emails', [])[:IDS_TO_TAKE])
        cookies = user_ids.get('cookies', [])

        hashed_id_values = set()
        hashed_id_values = hashed_id_values.union([cid for cid in self._ScoreStorageClass.convert_id_values_to_hashed_id_values(user_info)])

        if cookies:
            try:
                cookies = cookies[:IDS_TO_TAKE]
                new_ids = self._cookie_sync_api.lookup_cids(cookies)
                if new_ids:
                    hashed_id_values = hashed_id_values.union(new_ids)
            except Exception as ex:
                logger.warn('Exception durin cookie sync, {}'.format(ex))

        result_scores = []
        for partner_score_name in requested_partner_score_names:
            # YDB can not work with Unicode
            partner_score_name = str(partner_score_name)
            scores = []
            if hashed_id_values:
                score_dir_path = self._score_path_storage.get_score_path(partner_id, partner_score_name)
                score_storage = self._ScoreStorageClass(self._ydb_manager, self._connection_params.database, self._scores_root_path, score_dir_path)
                scores = score_storage.get_scores_for_hashed_id_values(hashed_id_values)
            if scores:
                result_scores.append(ScoreValue(partner_score_name, max(scores)))
            else:
                result_scores.append(ScoreValue(partner_score_name, None))
        return result_scores
