# -*- coding: utf-8 -*-
import json
from typing import List

from contextvars import ContextVar
from django.conf import settings
from django.db.models import Model, Q
from enum import Enum

from events.accounts.models import User
from events.surveyme.models import Survey, SurveyGroup
from events.v3.types import SurveyId, SubscriptionType


class DbTypes(Enum):
    slave = 0
    master = 1


class DbContext:
    def __init__(self, db_type: DbTypes = DbTypes.slave):
        self.db_type = db_type

    def copy(self):
        return DbContext(self.db_type)

    __copy__ = copy


MasterDbContext = DbContext(
    db_type=DbTypes.master,
)


SlaveDbContext = DbContext(
    db_type=DbTypes.slave,
)


_db_context_var = ContextVar('db_context')


def _get_db_context():
    try:
        return _db_context_var.get()
    except LookupError:
        context = DbContext()
        _db_context_var.set(context)
        return context


def _set_db_context(context):
    if context in(MasterDbContext, SlaveDbContext):
        context = context.copy()
    _db_context_var.set(context)


class _DbContextManager:
    def __init__(self, new_context):
        self.new_context = new_context.copy()

    def __enter__(self):
        self.saved_context = _get_db_context()
        _set_db_context(self.new_context)
        return self.new_context

    def __exit__(self, t, v, tb):
        _set_db_context(self.saved_context)


def set_master_db():
    return _DbContextManager(MasterDbContext)


def set_slave_db():
    return _DbContextManager(SlaveDbContext)


def get_from_master(model_class: Model, manager: str = None):
    manager = manager or 'objects'
    return getattr(model_class, manager).using(settings.DATABASE_DEFAULT)


def get_from_slave(model_class: Model, manager: str = None):
    manager = manager or 'objects'
    return getattr(model_class, manager).using(settings.DATABASE_ROLOCAL)


def get_from_db(model_class: Model, manager: str = None):
    ctx = _get_db_context()
    if ctx.db_type == DbTypes.slave:
        return get_from_slave(model_class, manager)
    elif ctx.db_type == DbTypes.master:
        return get_from_master(model_class, manager)
    raise TypeError


def json_loads(s: str):
    if not s:
        return s
    try:
        return json.loads(s)
    except (json.JSONDecodeError, TypeError):
        return s


def get_survey(
    survey_id: SurveyId,
    user: User, orgs: List[str] = None, only: List[str] = None,
) -> Survey:
    qs = get_from_db(Survey).select_related('org', 'group')
    if settings.IS_BUSINESS_SITE and not user.is_superuser:
        conditions = Q(org_id__isnull=True, user=user)
        if orgs:
            conditions |= Q(org__dir_id__in=orgs)
        qs = qs.filter(conditions)
    if only:
        if 'org__dir_id' not in only:
            only.append('org__dir_id')
        if 'group__id' not in only:
            only.append('group__id')
        qs = qs.only(*only)
    return qs.get(pk=survey_id)


def get_surveygroup(
    surveygroup_id: int,
    user: User, orgs: List[str] = None, only: List[str] = None,
) -> SurveyGroup:
    qs = get_from_db(SurveyGroup).select_related('org')
    if settings.IS_BUSINESS_SITE and not user.is_superuser:
        conditions = Q(org_id__isnull=True, user=user)
        if orgs:
            conditions |= Q(org__dir_id__in=orgs)
        qs = qs.filter(conditions)
    if only:
        if 'org__dir_id' not in only:
            only.append('org__dir_id')
        qs = qs.only(*only)
    return qs.get(pk=surveygroup_id)


def get_subscription_type(service_slug, action_slug) -> SubscriptionType:
    if service_slug == 'startrek':
        return SubscriptionType.tracker
    if service_slug == 'email':
        return SubscriptionType.email
    if service_slug == 'wiki':
        return SubscriptionType.wiki
    if service_slug == 'json_rpc':
        return SubscriptionType.jsonrpc
    if service_slug == 'http':
        if action_slug == 'post':
            return SubscriptionType.post
        if action_slug == 'put':
            return SubscriptionType.put
        if action_slug == 'arbitrary':
            return SubscriptionType.http


def get_subscription_type_by_action_id(action_id: int) -> SubscriptionType:
    match action_id:
        case 3:
            return SubscriptionType.email
        case 4:
            return SubscriptionType.post
        case 6:
            return SubscriptionType.jsonrpc
        case 7 | 8:
            return SubscriptionType.tracker
        case 9:
            return SubscriptionType.wiki
        case 10:
            return SubscriptionType.put
        case 11:
            return SubscriptionType.http
    raise ValueError(f'{action_id} does not exist')


def get_action_ids_by_subscription_type(subscription_type: SubscriptionType) -> List[int]:
    match subscription_type:
        case SubscriptionType.email:
            return (3, )
        case SubscriptionType.post:
            return (4, )
        case SubscriptionType.jsonrpc:
            return (6, )
        case SubscriptionType.tracker:
            return (7, 8)
        case SubscriptionType.wiki:
            return (9, )
        case SubscriptionType.put:
            return (10, )
        case SubscriptionType.http:
            return (11, )
    return tuple()
