import asyncio
import logging
from datetime import datetime
from typing import List, Union

import aiohttp
import orjson
import requests
from pydantic import BaseModel
from tabulate import tabulate

from ..model import (DTABLE_ICON_COLORS, DTABLE_ICON_LIST, Admin, ApiToken, Base, BaseToken, Column, Table, Team, User,
                     Webhook)
from .base import BaseClient
from .conf import SEATABLE_ACCOUNT_TOKEN, SEATABLE_PASSWORD, SEATABLE_URL, SEATABLE_USERNAME
from .core import TABULATE_CONF, HttpClient

logger = logging.getLogger()


################################################################
# AccountClient
################################################################
class AccountClient(HttpClient):
    def __init__(
        self,
        seatable_url: str = SEATABLE_URL,
        seatable_username: str = SEATABLE_USERNAME,
        seatable_password: str = SEATABLE_PASSWORD,
    ):
        super().__init__(seatable_url=seatable_url)
        self.username = seatable_username
        self.password = seatable_password
        self.account_token = None

        self.is_admin = False

        # do login
        self.login()

    def login(self):
        auth_url = self.seatable_url + "/api2/auth-token/"
        response = requests.post(auth_url, json={"username": self.username, "password": self.password})
        response.raise_for_status()
        results = response.json()
        self.account_token = results["token"]

    ################################################################
    # AUTHENTICATION - API TOKEN
    ################################################################
    # [API TOKEN] list api tokens
    async def list_api_tokens(self, workspace_id: str, base_name: str, model: BaseModel = ApiToken):
        """
        [NOTE]
         workspace id : group = 1 : 1
        """
        METHOD = "GET"
        URL = f"/api/v2.1/workspace/{workspace_id}/dtable/{base_name}/api-tokens/"
        ITEM = "api_tokens"

        async with self.session_maker(token=self.account_token) as session:
            response = await self.request(session=session, method=METHOD, url=URL)
            results = response[ITEM]

        if model:
            results = [model(**x) for x in results]

        return results

    # [API TOKEN] create api token
    async def get_or_create_api_token(
        self,
        workspace_id: str,
        base_name: str,
        app_name: str,
        permission: str = "rw",
        model: BaseModel = ApiToken,
    ):
        """
        [NOTE]
         "bad request" returns if app_name is already exists.
        """
        api_tokens = await self.list_api_tokens(workspace_id=workspace_id, base_name=base_name)
        for api_token in api_tokens:
            if api_token.app_name == app_name:
                return api_token

        METHOD = "POST"
        URL = f"/api/v2.1/workspace/{workspace_id}/dtable/{base_name}/api-tokens/"
        JSON = {"app_name": app_name, "permission": permission}

        async with self.session_maker(token=self.account_token) as session:
            response = await self.request(session=session, method=METHOD, url=URL, json=JSON)
            results = model(**response) if model else response

        return results

    # [API TOKEN] create temporary api token
    async def create_temp_api_token(self, workspace_id: str, base_name: str, model: BaseModel = ApiToken):
        METHOD = "GET"
        URL = f"/api/v2.1/workspace/{workspace_id}/dtable/{base_name}/temp-api-token/"
        ITEM = "api_token"

        async with self.session_maker(token=self.account_token) as session:
            response = await self.request(session=session, method=METHOD, url=URL)
            results = response[ITEM]

        if model:
            now = datetime.now()
            results = model(
                app_name="__temp_token",
                api_token=results,
                generated_by="__temp_token",
                generated_at=now,
                last_access=now,
                permission="r",
            )

        return results

    # [API TOKEN] update api token
    async def update_api_token(
        self,
        workspace_id: str,
        base_name: str,
        app_name: str,
        permission: str = "rw",
        model: BaseModel = ApiToken,
    ):
        METHOD = "PUT"
        URL = f"/api/v2.1/workspace/{workspace_id}/dtable/{base_name}/api-tokens/{app_name}"
        JSON = {"permission": permission}

        async with self.session_maker(token=self.account_token) as session:
            response = await self.request(session=session, method=METHOD, url=URL, json=JSON)
            results = model(**response) if model else response

        return results

    # [API TOKEN] delete api token
    async def delete_api_token(self, workspace_id: str, base_name: str, app_name: str):
        METHOD = "DELETE"
        URL = f"/api/v2.1/workspace/{workspace_id}/dtable/{base_name}/api-tokens/{app_name}"
        ITEM = "success"

        async with self.session_maker(token=self.account_token) as session:
            response = await self.request(session=session, method=METHOD, url=URL)
            results = response[ITEM]

        return results

    ################################################################
    # AUTHENTICATION - BASE TOKEN
    ################################################################
    # [BASE TOKEN] get base token with account token
    async def get_base_token_with_account_token(
        self,
        workspace_id: str = None,
        base_name: str = None,
        model: BaseModel = BaseToken,
    ):
        METHOD = "GET"
        URL = f"/api/v2.1/workspace/{workspace_id}/dtable/{base_name}/access-token/"

        async with self.session_maker(token=self.account_token) as session:
            results = await self.request(session=session, method=METHOD, url=URL)
        if model:
            results = model(**results)

        return results

    # [BASE TOKEN] get base token with api token
    async def get_base_token_with_api_token(self, api_token: str, model: BaseModel = BaseToken):
        METHOD = "GET"
        URL = "/api/v2.1/dtable/app-access-token/"

        async with self.session_maker(token=api_token) as session:
            results = await self.request(session=session, method=METHOD, url=URL)
        if model:
            results = model(**results)

        return results

    # [BASE TOKEN] get base token with invite link
    async def get_base_token_with_invite_link(self, link: str, model: BaseModel = BaseToken):
        link = link.rsplit("/links/", 1)[-1].strip("/")
        METHOD = "GET"
        URL = "/api/v2.1/dtable/share-link-access-token/"

        async with self.session_maker(token=link) as session:
            results = await self.request(session=session, method=METHOD, url=URL)
        if model:
            results = model(**results)

        return results

    # [BASE TOKEN] get base token with external link
    async def get_base_token_with_external_link(self, link: str, model: BaseModel = BaseToken):
        link = link.rsplit("/external-links/", 1)[-1].strip("/")
        METHOD = "GET"
        URL = f"/api/v2.1/external-link-tokens/{link}/access-token/"

        async with self.session_maker(token=link) as session:
            results = await self.request(session=session, method=METHOD, url=URL)
        if model:
            results = model(**results)

        return results

    ################################################################
    # (CUSTOM) GET BASE CLIENT
    ################################################################
    # [BASE CLIENT] (custom) get base client with account token
    async def get_base_client_with_account_token(self, workspace_id: str, base_name: str):
        base_token = await self.get_base_token_with_account_token(workspace_id=workspace_id, base_name=base_name)
        return BaseClient(base_token=base_token)
