#!/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)

[((1030, 1172), 'sqlalchemy.select', 'select', (['[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])\n', (1036, 1172), False, 'from sqlalchemy import select, and_\n'), ((2830, 3006), 'epicteller.core.util.ObjectDict', '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'}), '(url_token=url_token, member_id=member_id, name=name, avatar=\n    avatar, description=description, is_removed=0, data=raw_data, created=\n    created, updated=created)\n', (2840, 3006), False, 'from epicteller.core.util import ObjectDict\n'), ((4563, 4580), 'collections.defaultdict', 'defaultdict', (['list'], {}), '(list)\n', (4574, 4580), False, 'from collections import defaultdict\n'), ((1467, 1487), 'epicteller.core.tables.table.execute', 'table.execute', (['query'], {}), '(query)\n', (1480, 1487), False, 'from epicteller.core.tables import table\n'), ((1811, 1831), 'epicteller.core.tables.table.execute', 'table.execute', (['query'], {}), '(query)\n', (1824, 1831), False, 'from epicteller.core.tables import table\n'), ((2138, 2158), 'epicteller.core.tables.table.execute', 'table.execute', (['query'], {}), '(query)\n', (2151, 2158), False, 'from epicteller.core.tables import table\n'), ((2540, 2560), 'epicteller.core.tables.table.execute', 'table.execute', (['query'], {}), '(query)\n', (2553, 2560), False, 'from epicteller.core.tables import table\n'), ((2756, 2767), 'time.time', 'time.time', ([], {}), '()\n', (2765, 2767), False, 'import time\n'), ((2803, 2811), 'epicteller.core.util.seq.get_id', 'get_id', ([], {}), '()\n', (2809, 2811), False, 'from epicteller.core.util.seq import get_id\n'), ((3185, 3205), 'epicteller.core.tables.table.execute', 'table.execute', (['query'], {}), '(query)\n', (3198, 3205), False, 'from epicteller.core.tables import table\n'), ((3554, 3616), 'sqlalchemy.and_', 'and_', (['(cls.t.c.campaign_id == campaign_id)', '(cls.t.c.name == name)'], {}), '(cls.t.c.campaign_id == campaign_id, cls.t.c.name == name)\n', (3558, 3616), False, 'from sqlalchemy import select, and_\n'), ((3699, 3719), 'epicteller.core.tables.table.execute', 'table.execute', (['query'], {}), '(query)\n', (3712, 3719), False, 'from epicteller.core.tables import table\n'), ((4049, 4069), 'epicteller.core.tables.table.execute', 'table.execute', (['query'], {}), '(query)\n', (4062, 4069), False, 'from epicteller.core.tables import table\n'), ((4479, 4499), 'epicteller.core.tables.table.execute', 'table.execute', (['query'], {}), '(query)\n', (4492, 4499), False, 'from epicteller.core.tables import table\n'), ((5039, 5059), 'epicteller.core.tables.table.execute', 'table.execute', (['query'], {}), '(query)\n', (5052, 5059), False, 'from epicteller.core.tables import table\n'), ((5201, 5279), 'sqlalchemy.and_', 'and_', (['(cls.t.c.character_id == character_id)', '(cls.t.c.campaign_id == campaign_id)'], {}), '(cls.t.c.character_id == character_id, cls.t.c.campaign_id == campaign_id)\n', (5205, 5279), False, 'from sqlalchemy import select, and_\n'), ((5295, 5315), 'epicteller.core.tables.table.execute', 'table.execute', (['query'], {}), '(query)\n', (5308, 5315), False, 'from epicteller.core.tables import table\n'), ((5658, 5678), 'epicteller.core.tables.table.execute', 'table.execute', (['query'], {}), '(query)\n', (5671, 5678), False, 'from epicteller.core.tables import table\n'), ((5739, 5761), 'epicteller.core.util.enum.ExternalType', 'ExternalType', (['row.type'], {}), '(row.type)\n', (5751, 5761), False, 'from epicteller.core.util.enum import ExternalType\n'), ((6161, 6181), 'epicteller.core.tables.table.execute', 'table.execute', (['query'], {}), '(query)\n', (6174, 6181), False, 'from epicteller.core.tables import table\n'), ((6689, 6709), 'epicteller.core.tables.table.execute', 'table.execute', (['query'], {}), '(query)\n', (6702, 6709), False, 'from epicteller.core.tables import table\n'), ((6956, 6976), 'epicteller.core.tables.table.execute', 'table.execute', (['query'], {}), '(query)\n', (6969, 6976), False, 'from epicteller.core.tables import table\n'), ((2433, 2444), 'time.time', 'time.time', ([], {}), '()\n', (2442, 2444), False, 'import time\n'), ((3517, 3547), 'sqlalchemy.select', 'select', (['[cls.t.c.character_id]'], {}), '([cls.t.c.character_id])\n', (3523, 3547), False, 'from sqlalchemy import select, and_\n'), ((3952, 3982), 'sqlalchemy.select', 'select', (['[cls.t.c.character_id]'], {}), '([cls.t.c.character_id])\n', (3958, 3982), False, 'from sqlalchemy import select, and_\n'), ((4321, 4372), 'sqlalchemy.select', 'select', (['[cls.t.c.character_id, cls.t.c.campaign_id]'], {}), '([cls.t.c.character_id, cls.t.c.campaign_id])\n', (4327, 4372), False, 'from sqlalchemy import select, and_\n'), ((5512, 5555), 'sqlalchemy.select', 'select', (['[cls.t.c.type, cls.t.c.external_id]'], {}), '([cls.t.c.type, cls.t.c.external_id])\n', (5518, 5555), False, 'from sqlalchemy import select, and_\n'), ((5965, 5995), 'sqlalchemy.select', 'select', (['[cls.t.c.character_id]'], {}), '([cls.t.c.character_id])\n', (5971, 5995), False, 'from sqlalchemy import select, and_\n'), ((4830, 4849), 'sqlalchemy.dialects.mysql.insert', 'mysql_insert', (['cls.t'], {}), '(cls.t)\n', (4842, 4849), True, 'from sqlalchemy.dialects.mysql import insert as mysql_insert\n'), ((6452, 6471), 'sqlalchemy.dialects.mysql.insert', 'mysql_insert', (['cls.t'], {}), '(cls.t)\n', (6464, 6471), True, 'from sqlalchemy.dialects.mysql import insert as mysql_insert\n')]