#!/usr/bin/env python
# -*- coding: utf-8 -*-

import time
from collections import defaultdict
from typing import List, Optional, Iterable, Dict

import base62
from sqlalchemy import select, and_
from sqlalchemy.dialects.mysql import insert as mysql_insert

from epicteller.core.model.character import Character
from epicteller.core.tables import table
from epicteller.core.util import ObjectDict
from epicteller.core.util.enum import ExternalType
from epicteller.core.util.seq import get_id


def _format_character(result) -> Optional[Character]:
    if not result:
        return
    character = Character(
        id=result.id,
        url_token=result.url_token,
        member_id=result.member_id,
        name=result.name,
        avatar=result.avatar,
        description=result.description,
        is_removed=bool(result.is_removed),
        raw_data=result.data,
        created=result.created,
        updated=result.updated,
    )
    return character


class CharacterDAO:
    t = table.character
    select_clause = select([
        t.c.id,
        t.c.url_token,
        t.c.name,
        t.c.member_id,
        t.c.avatar,
        t.c.description,
        t.c.is_removed,
        t.c.data,
        t.c.created,
        t.c.updated,
    ])

    @classmethod
    async def batch_get_character_by_id(cls, character_ids: Iterable[int]) -> Dict[int, Character]:
        query = cls.select_clause.where(cls.t.c.id.in_(character_ids))
        result = await table.execute(query)
        rows = await result.fetchall()
        return {row.id: _format_character(row) for row in rows}

    @classmethod
    async def batch_get_character_by_url_token(cls, url_tokens: Iterable[str]) -> Dict[str, Character]:
        query = cls.select_clause.where(cls.t.c.url_token.in_(url_tokens))
        result = await table.execute(query)
        rows = await result.fetchall()
        return {row.url_token: _format_character(result) for row in rows}

    @classmethod
    async def get_characters_by_owner(cls, member_id: int) -> List[Character]:
        query = cls.select_clause.where(cls.t.c.member_id == member_id)
        results = await table.execute(query)
        characters = [_format_character(room) for room in await results.fetchall()]
        return characters

    @classmethod
    async def update_character(cls, character_id: int, **kwargs) -> None:
        if 'updated' not in kwargs:
            kwargs['updated'] = int(time.time())
        query = cls.t.update().values(kwargs).where(cls.t.c.id == character_id)
        await table.execute(query)

    @classmethod
    async def create_character(cls, member_id: int, name: str, avatar: str, description: str,
                               raw_data: dict) -> Character:
        created = int(time.time())
        url_token = base62.encode(get_id())
        values = ObjectDict(
            url_token=url_token,
            member_id=member_id,
            name=name,
            avatar=avatar,
            description=description,
            is_removed=0,
            data=raw_data,
            created=created,
            updated=created,
        )
        query = cls.t.insert().values(values)
        result = await table.execute(query)
        values.id = result.lastrowid
        character = _format_character(values)
        return character


class CharacterCampaignDAO:
    t = table.character_campaign_index

    @classmethod
    async def get_character_id_by_campaign_name(cls, campaign_id: int, name: str) -> Optional[int]:
        query = select([cls.t.c.character_id]).where(and_(cls.t.c.campaign_id == campaign_id,
                                                          cls.t.c.name == name))
        result = await table.execute(query)
        row = await result.fetchone()
        if not row:
            return
        return int(row.character_id)

    @classmethod
    async def get_character_ids_by_campaign_id(cls, campaign_id: int) -> List[int]:
        query = select([cls.t.c.character_id]).where(cls.t.c.campaign_id == campaign_id)
        results = await table.execute(query)
        character_ids = [int(row.character_id) for row in await results.fetchall()]
        return character_ids

    @classmethod
    async def get_campaign_ids_by_character_ids(cls, character_ids: List[int]) -> Dict[int, List[int]]:
        query = select([
            cls.t.c.character_id,
            cls.t.c.campaign_id,
        ]).where(cls.t.c.character_id.in_(character_ids))
        results = await table.execute(query)
        rows = await results.fetchall()
        campaign_map = defaultdict(list)
        for r in rows:
            campaign_map[r.character_id].append(r.campaign_id)
        return dict(campaign_map)

    @classmethod
    async def bind_character_to_campaign(cls, character_id: int, name: str, campaign_id: int):
        query = mysql_insert(cls.t).values(
            character_id=character_id,
            name=name,
            campaign_id=campaign_id,
        ).on_duplicate_key_update(
            name=name,
        )
        await table.execute(query)

    @classmethod
    async def unbind_character_to_campaign(cls, character_id: int, campaign_id: int):
        query = cls.t.delete().where(and_(cls.t.c.character_id == character_id, cls.t.c.campaign_id == campaign_id))
        await table.execute(query)


class CharacterExternalDAO:
    t = table.character_external_id

    @classmethod
    async def get_external_ids_by_character(cls, character_id: int) -> Dict[ExternalType, str]:
        query = select([
            cls.t.c.type,
            cls.t.c.external_id,
        ]).where(cls.t.c.character_id == character_id)
        result = await table.execute(query)
        rows = await result.fetchall()
        externals = {ExternalType(row.type): row.external_id for row in rows}
        return externals

    @classmethod
    async def get_character_ids_by_external(cls, external_type: ExternalType, external_id: str) -> List[int]:
        query = select([cls.t.c.character_id]).where(and_(cls.t.c.type == int(external_type),
                                                          cls.t.c.external_id == external_id))
        result = await table.execute(query)
        rows = await result.fetchall()
        character_ids = [r.character_id for r in rows]
        return character_ids

    @classmethod
    async def bind_character_external_id(cls, character_id: int, external_type: ExternalType, external_id: str):
        query = mysql_insert(cls.t).values(
            character_id=character_id,
            type=int(external_type),
            external_id=external_id,
        ).on_duplicate_key_update(
            external_id=external_id,
        )
        await table.execute(query)

    @classmethod
    async def unbind_character_external_id(cls, character_id: int, external_type: ExternalType):
        query = cls.t.delete().where(and_(cls.t.c.character_id == character_id, cls.t.c.type == int(external_type)))
        await table.execute(query)

[((39, 20, 50, 6), 'sqlalchemy.select', 'select', ({(39, 27, 50, 5): '[t.c.id, t.c.url_token, t.c.name, t.c.member_id, t.c.avatar, t.c.\n    description, t.c.is_removed, t.c.data, t.c.created, t.c.updated]'}, {}), '([t.c.id, t.c.url_token, t.c.name, t.c.member_id, t.c.avatar, t.c.\n    description, t.c.is_removed, t.c.data, t.c.created, t.c.updated])', False, 'from sqlalchemy import select, and_\n'), ((85, 17, 95, 9), 'epicteller.core.util.ObjectDict', 'ObjectDict', (), '', False, 'from epicteller.core.util import ObjectDict\n'), ((131, 23, 131, 40), 'collections.defaultdict', 'defaultdict', ({(131, 35, 131, 39): 'list'}, {}), '(list)', False, 'from collections import defaultdict\n'), ((55, 23, 55, 43), 'epicteller.core.tables.table.execute', 'table.execute', ({(55, 37, 55, 42): 'query'}, {}), '(query)', False, 'from epicteller.core.tables import table\n'), ((62, 23, 62, 43), 'epicteller.core.tables.table.execute', 'table.execute', ({(62, 37, 62, 42): 'query'}, {}), '(query)', False, 'from epicteller.core.tables import table\n'), ((69, 24, 69, 44), 'epicteller.core.tables.table.execute', 'table.execute', ({(69, 38, 69, 43): 'query'}, {}), '(query)', False, 'from epicteller.core.tables import table\n'), ((78, 14, 78, 34), 'epicteller.core.tables.table.execute', 'table.execute', ({(78, 28, 78, 33): 'query'}, {}), '(query)', False, 'from epicteller.core.tables import table\n'), ((83, 22, 83, 33), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((84, 34, 84, 42), 'epicteller.core.util.seq.get_id', 'get_id', ({}, {}), '()', False, 'from epicteller.core.util.seq import get_id\n'), ((97, 23, 97, 43), 'epicteller.core.tables.table.execute', 'table.execute', ({(97, 37, 97, 42): 'query'}, {}), '(query)', False, 'from epicteller.core.tables import table\n'), ((108, 53, 109, 79), 'sqlalchemy.and_', 'and_', ({(108, 58, 108, 92): 'cls.t.c.campaign_id == campaign_id', (109, 58, 109, 78): 'cls.t.c.name == name'}, {}), '(cls.t.c.campaign_id == campaign_id, cls.t.c.name == name)', False, 'from sqlalchemy import select, and_\n'), ((110, 23, 110, 43), 'epicteller.core.tables.table.execute', 'table.execute', ({(110, 37, 110, 42): 'query'}, {}), '(query)', False, 'from epicteller.core.tables import table\n'), ((119, 24, 119, 44), 'epicteller.core.tables.table.execute', 'table.execute', ({(119, 38, 119, 43): 'query'}, {}), '(query)', False, 'from epicteller.core.tables import table\n'), ((129, 24, 129, 44), 'epicteller.core.tables.table.execute', 'table.execute', ({(129, 38, 129, 43): 'query'}, {}), '(query)', False, 'from epicteller.core.tables import table\n'), ((145, 14, 145, 34), 'epicteller.core.tables.table.execute', 'table.execute', ({(145, 28, 145, 33): 'query'}, {}), '(query)', False, 'from epicteller.core.tables import table\n'), ((149, 37, 149, 115), 'sqlalchemy.and_', 'and_', ({(149, 42, 149, 78): 'cls.t.c.character_id == character_id', (149, 80, 149, 114): 'cls.t.c.campaign_id == campaign_id'}, {}), '(cls.t.c.character_id == character_id, cls.t.c.campaign_id == campaign_id)', False, 'from sqlalchemy import select, and_\n'), ((150, 14, 150, 34), 'epicteller.core.tables.table.execute', 'table.execute', ({(150, 28, 150, 33): 'query'}, {}), '(query)', False, 'from epicteller.core.tables import table\n'), ((162, 23, 162, 43), 'epicteller.core.tables.table.execute', 'table.execute', ({(162, 37, 162, 42): 'query'}, {}), '(query)', False, 'from epicteller.core.tables import table\n'), ((164, 21, 164, 43), 'epicteller.core.util.enum.ExternalType', 'ExternalType', ({(164, 34, 164, 42): 'row.type'}, {}), '(row.type)', False, 'from epicteller.core.util.enum import ExternalType\n'), ((171, 23, 171, 43), 'epicteller.core.tables.table.execute', 'table.execute', ({(171, 37, 171, 42): 'query'}, {}), '(query)', False, 'from epicteller.core.tables import table\n'), ((185, 14, 185, 34), 'epicteller.core.tables.table.execute', 'table.execute', ({(185, 28, 185, 33): 'query'}, {}), '(query)', False, 'from epicteller.core.tables import table\n'), ((190, 14, 190, 34), 'epicteller.core.tables.table.execute', 'table.execute', ({(190, 28, 190, 33): 'query'}, {}), '(query)', False, 'from epicteller.core.tables import table\n'), ((76, 36, 76, 47), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((108, 16, 108, 46), 'sqlalchemy.select', 'select', ({(108, 23, 108, 45): '[cls.t.c.character_id]'}, {}), '([cls.t.c.character_id])', False, 'from sqlalchemy import select, and_\n'), ((118, 16, 118, 46), 'sqlalchemy.select', 'select', ({(118, 23, 118, 45): '[cls.t.c.character_id]'}, {}), '([cls.t.c.character_id])', False, 'from sqlalchemy import select, and_\n'), ((125, 16, 128, 10), 'sqlalchemy.select', 'select', ({(125, 23, 128, 9): '[cls.t.c.character_id, cls.t.c.campaign_id]'}, {}), '([cls.t.c.character_id, cls.t.c.campaign_id])', False, 'from sqlalchemy import select, and_\n'), ((158, 16, 161, 10), 'sqlalchemy.select', 'select', ({(158, 23, 161, 9): '[cls.t.c.type, cls.t.c.external_id]'}, {}), '([cls.t.c.type, cls.t.c.external_id])', False, 'from sqlalchemy import select, and_\n'), ((169, 16, 169, 46), 'sqlalchemy.select', 'select', ({(169, 23, 169, 45): '[cls.t.c.character_id]'}, {}), '([cls.t.c.character_id])', False, 'from sqlalchemy import select, and_\n'), ((138, 16, 138, 35), 'sqlalchemy.dialects.mysql.insert', 'mysql_insert', ({(138, 29, 138, 34): 'cls.t'}, {}), '(cls.t)', True, 'from sqlalchemy.dialects.mysql import insert as mysql_insert\n'), ((178, 16, 178, 35), 'sqlalchemy.dialects.mysql.insert', 'mysql_insert', ({(178, 29, 178, 34): 'cls.t'}, {}), '(cls.t)', True, 'from sqlalchemy.dialects.mysql import insert as mysql_insert\n')]