# -*- coding: utf-8 -*-
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


logger = logging.getLogger(__name__)


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


REQUEST_LOG_TAG = 'score_value_request'


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"]
}


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, scores_storage=None, crypta_storage=None,
                 depricated_crypta_storage=None, cookie_sync_api=None, depricated_scores=None, **kwargs):
        """
        Params:
            deoricated_scores(set([str,]), default=None) : Depricated scores from Crypta v1.
                Will use crypta_v1 tables, and will be disabled in future.
        """
        super(ScoreValueView, self).__init__(**kwargs)
        self._scores = scores_storage
        self._crypta = crypta_storage
        self._cookie_sync_api = cookie_sync_api

        # hack to support both version of Crypta, remove later
        self._depricated_crypta = depricated_crypta_storage
        self._depricated_scores = depricated_scores or set()

    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(current_user, partner_id, request_body)
            except Exception:
                with log_context(tag=REQUEST_LOG_TAG, success=False):
                    logger.info('score values request processing fail')
                raise

            response_scores = []
            for s, value in score_values:
                r = {
                    'score_name': s.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, user, partner_id, query):
        enabled_scores = self._scores.get_user_scores(user)
        requested_names = {s['score_name'] for s in query.get('scores', [])}
        enabled_names = {s.name for s in enabled_scores if s.partner_id == partner_id}
        if requested_names - enabled_names:
            raise ScoreReadForbidden()
        requested_scores = [s for s in enabled_scores if s.name in requested_names and s.partner_id == partner_id]
        normal_requested_scores, depricated_requested_scores = [], []
        for score in requested_scores:
            if score.name in self._depricated_scores:
                depricated_requested_scores.append(score)
            else:
                normal_requested_scores.append(score)

        user_ids = query.get('user_ids')
        user_info = []
        user_info.extend(user_ids.get('phones', []))
        user_info.extend(user_ids.get('emails', []))
        cookies = user_ids.get('cookies', [])
        casual_cookies, sync_cookies = self._cookie_sync_api.separate_casual_and_sync_cookies(cookies)

        cids = set()
        if normal_requested_scores:
            cids = cids.union(self._crypta.lookup_cids(user_info, casual_cookies))

        # TODO: Remove when CryptaV1 scores will be disabled
        if depricated_requested_scores:
            cids = cids.union(self._depricated_crypta.lookup_cids(user_info, casual_cookies))

        if sync_cookies:
            cids = cids.union(self._cookie_sync_api.lookup_cids(sync_cookies))

        return self._scores.get_scores_value(partner_id, cids, requested_scores)
