# Released under the MIT License. See LICENSE for details.
#
"""Cloud related functionality.

.. warning::

  This is an internal api and subject to change at any time. Do not use
  it in mod code.
"""

from __future__ import annotations

from enum import Enum
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Annotated, override

from efro.message import Message, Response
from efro.dataclassio import ioprepped, IOAttrs
from bacommon.analytics import AnalyticsEvent
from bacommon.securedata import SecureDataChecker
from bacommon.transfer import DirectoryManifest
from bacommon.login import LoginType
from bacommon.docui import DocUIRequest, DocUIResponse
import bacommon.displayitem as ditm
import bacommon.clienteffect as clfx

if TYPE_CHECKING:
    pass


class WebLocation(Enum):
    """Set of places we can be directed on ballistica.net."""

    ACCOUNT_EDITOR = 'e'
    ACCOUNT_DELETE_SECTION = 'd'


@ioprepped
@dataclass
class CloudVals:
    """Engine config values provided by the master server.

    Used to convey things such as debug logging.
    """

    #: Fully qualified type names we should emit extra debug logs for
    #: when garbage-collected (for debugging ref loops).
    gc_debug_types: Annotated[
        list[str], IOAttrs('gct', store_default=False)
    ] = field(default_factory=list)

    #: Max number of objects of a given type to emit debug logs for.
    gc_debug_type_limit: Annotated[int, IOAttrs('gdl', store_default=False)] = 2


@ioprepped
@dataclass
class LoginProxyRequestMessage(Message):
    """Request send to the cloud to ask for a login-proxy."""

    @override
    @classmethod
    def get_response_types(cls) -> list[type[Response] | None]:
        return [LoginProxyRequestResponse]


@ioprepped
@dataclass
class LoginProxyRequestResponse(Response):
    """Response to a request for a login proxy."""

    # URL to direct the user to for sign in.
    url: Annotated[str, IOAttrs('u')]

    # URL to use for overlay-web-browser sign ins.
    url_overlay: Annotated[str, IOAttrs('uo')]

    # Proxy-Login id for querying results.
    proxyid: Annotated[str, IOAttrs('p')]

    # Proxy-Login key for querying results.
    proxykey: Annotated[str, IOAttrs('k')]


@ioprepped
@dataclass
class LoginProxyStateQueryMessage(Message):
    """Soo.. how is that login proxy going?"""

    proxyid: Annotated[str, IOAttrs('p')]
    proxykey: Annotated[str, IOAttrs('k')]

    @override
    @classmethod
    def get_response_types(cls) -> list[type[Response] | None]:
        return [LoginProxyStateQueryResponse]


@ioprepped
@dataclass
class LoginProxyStateQueryResponse(Response):
    """Here's the info on that login-proxy you asked about, boss."""

    class State(Enum):
        """States a login-proxy can be in."""

        WAITING = 'waiting'
        SUCCESS = 'success'
        FAIL = 'fail'

    state: Annotated[State, IOAttrs('s')]

    # On success, these will be filled out.
    credentials: Annotated[str | None, IOAttrs('tk')]


@ioprepped
@dataclass
class LoginProxyCompleteMessage(Message):
    """Just so you know, we're done with this proxy."""

    proxyid: Annotated[str, IOAttrs('p')]


@ioprepped
@dataclass
class PingMessage(Message):
    """Standard ping."""

    @override
    @classmethod
    def get_response_types(cls) -> list[type[Response] | None]:
        return [PingResponse]


@ioprepped
@dataclass
class PingResponse(Response):
    """pong."""


@ioprepped
@dataclass
class TestMessage(Message):
    """Can I get some of that workspace action?"""

    testfoo: Annotated[int, IOAttrs('f')]

    @override
    @classmethod
    def get_response_types(cls) -> list[type[Response] | None]:
        return [TestResponse]


@ioprepped
@dataclass
class TestResponse(Response):
    """Here's that workspace you asked for, boss."""

    testfoo: Annotated[int, IOAttrs('f')]


@ioprepped
@dataclass
class WorkspaceFetchState:
    """Common state data for a workspace fetch."""

    manifest: Annotated[DirectoryManifest, IOAttrs('m')]
    iteration: Annotated[int, IOAttrs('i')] = 0
    total_deletes: Annotated[int, IOAttrs('tdels')] = 0
    total_downloads: Annotated[int, IOAttrs('tdlds')] = 0
    total_up_to_date: Annotated[int | None, IOAttrs('tunmd')] = None


@ioprepped
@dataclass
class WorkspaceFetchMessage(Message):
    """Can I get some of that workspace action?"""

    workspaceid: Annotated[str, IOAttrs('w')]
    state: Annotated[WorkspaceFetchState, IOAttrs('s')]

    @override
    @classmethod
    def get_response_types(cls) -> list[type[Response] | None]:
        return [WorkspaceFetchResponse]


@ioprepped
@dataclass
class WorkspaceFetchResponse(Response):
    """Here's that workspace you asked for, boss."""

    state: Annotated[WorkspaceFetchState, IOAttrs('s')]
    deletes: Annotated[list[str], IOAttrs('dlt', store_default=False)] = field(
        default_factory=list
    )
    downloads_inline: Annotated[
        dict[str, bytes], IOAttrs('dinl', store_default=False)
    ] = field(default_factory=dict)

    done: Annotated[bool, IOAttrs('d')] = False

    #: If set, the client should treat the sync as failed and display
    #: this message. Allows the server to communicate user-facing errors
    #: without relying on the protocol's ``forward_clean_errors`` flag.
    error: Annotated[
        str | None, IOAttrs('e', soft_default=None, store_default=False)
    ] = None


@ioprepped
@dataclass
class MerchAvailabilityMessage(Message):
    """Can we show merch link?"""

    @override
    @classmethod
    def get_response_types(cls) -> list[type[Response] | None]:
        return [MerchAvailabilityResponse]


@ioprepped
@dataclass
class MerchAvailabilityResponse(Response):
    """About that merch..."""

    url: Annotated[str | None, IOAttrs('u')]


@ioprepped
@dataclass
class SignInMessage(Message):
    """Can I sign in please?"""

    login_type: Annotated[LoginType, IOAttrs('l')]
    sign_in_token: Annotated[str, IOAttrs('t')]

    # For debugging. Can remove soft_default once build 20988+ is ubiquitous.
    description: Annotated[str, IOAttrs('d', soft_default='-')]
    apptime: Annotated[float, IOAttrs('at', soft_default=-1.0)]

    @override
    @classmethod
    def get_response_types(cls) -> list[type[Response] | None]:
        return [SignInResponse]


@ioprepped
@dataclass
class SignInResponse(Response):
    """Here's that sign-in result you asked for, boss."""

    credentials: Annotated[str | None, IOAttrs('c')]


@ioprepped
@dataclass
class ManageAccountMessage(Message):
    """Message asking for a manage-account url."""

    weblocation: Annotated[WebLocation, IOAttrs('l')] = (
        WebLocation.ACCOUNT_EDITOR
    )

    @override
    @classmethod
    def get_response_types(cls) -> list[type[Response] | None]:
        return [ManageAccountResponse]


@ioprepped
@dataclass
class ManageAccountResponse(Response):
    """Here's that sign-in result you asked for, boss."""

    url: Annotated[str | None, IOAttrs('u')]


@ioprepped
@dataclass
class StoreQueryMessage(Message):
    """Message asking about purchasable stuff and store related state."""

    @override
    @classmethod
    def get_response_types(cls) -> list[type[Response] | None]:
        return [StoreQueryResponse]


@ioprepped
@dataclass
class StoreQueryResponse(Response):
    """Here's that store info you asked for, boss."""

    class Result(Enum):
        """Our overall result."""

        SUCCESS = 's'
        ERROR = 'e'

    @dataclass
    class Purchase:
        """Info about a purchasable thing."""

        purchaseid: Annotated[str, IOAttrs('id')]

    # Overall result; all data is undefined if not SUCCESS.
    result: Annotated[Result, IOAttrs('r')]

    tokens: Annotated[int, IOAttrs('t')]
    gold_pass: Annotated[bool, IOAttrs('g')]

    available_purchases: Annotated[list[Purchase], IOAttrs('p')]
    token_info_url: Annotated[str, IOAttrs('tiu')]


@ioprepped
@dataclass
class SecureDataCheckerRequest(Message):
    """Can I get a checker over here?."""

    @override
    @classmethod
    def get_response_types(cls) -> list[type[Response] | None]:
        return [SecureDataCheckerResponse]


@ioprepped
@dataclass
class SecureDataCheckerResponse(Response):
    """Here's that checker ya asked for, boss."""

    checker: Annotated[SecureDataChecker, IOAttrs('c')]


@ioprepped
@dataclass
class CloudValsRequest(Message):
    """Can a fella get some cloud vals around here?."""

    @override
    @classmethod
    def get_response_types(cls) -> list[type[Response] | None]:
        return [CloudValsResponse]


@ioprepped
@dataclass
class CloudValsResponse(Response):
    """Here's them cloud vals ya asked for, boss."""

    vals: Annotated[CloudVals, IOAttrs('v')]


@ioprepped
@dataclass
class ChestActionMessage(Message):
    """Request action about a chest."""

    class Action(Enum):
        """Types of actions we can request."""

        # Unlocking (for free or with tokens).
        UNLOCK = 'u'

        # Watched an ad to reduce wait.
        AD = 'ad'

    action: Annotated[Action, IOAttrs('a')]

    # Tokens we are paying (only applies to unlock).
    token_payment: Annotated[int, IOAttrs('t')]

    chest_id: Annotated[str, IOAttrs('i')]

    @override
    @classmethod
    def get_response_types(cls) -> list[type[Response] | None]:
        return [ChestActionResponse]


@ioprepped
@dataclass
class ChestActionResponse(Response):
    """Here's the results of that action you asked for, boss."""

    # Tokens that were actually charged.
    tokens_charged: Annotated[int, IOAttrs('t')] = 0

    # If present, signifies the chest has been opened and we should show
    # the user this stuff that was in it.
    contents: Annotated[list[ditm.Wrapper] | None, IOAttrs('c')] = None

    # If contents are present, which of the chest's prize-sets they
    # represent.
    prizeindex: Annotated[int, IOAttrs('i')] = 0

    # Printable error if something goes wrong.
    error: Annotated[str | None, IOAttrs('e')] = None

    # Printable warning. Shown in orange with an error sound. Does not
    # mean the action failed; only that there's something to tell the
    # users such as 'It looks like you are faking ad views; stop it or
    # you won't have ad options anymore.'
    warning: Annotated[str | None, IOAttrs('w', store_default=False)] = None

    # Printable success message. Shown in green with a cash-register
    # sound. Can be used for things like successful wait reductions via
    # ad views. Used in builds earlier than 22311; can remove once
    # 22311+ is ubiquitous.
    success_msg: Annotated[str | None, IOAttrs('s', store_default=False)] = None

    # Effects to show on the client. Replaces warning and success_msg in
    # build 22311 or newer.
    effects: Annotated[
        list[clfx.Effect], IOAttrs('fx', store_default=False)
    ] = field(default_factory=list)


@ioprepped
@dataclass
class FulfillDocUIRequest(Message):
    """Can a fella get a doc-ui round here?"""

    request: Annotated[DocUIRequest, IOAttrs('r')]
    domain: Annotated[str, IOAttrs('d')]

    @override
    @classmethod
    def get_response_types(cls) -> list[type[Response] | None]:
        return [FulfillDocUIResponse]


@ioprepped
@dataclass
class FulfillDocUIResponse(Response):
    """Here's that doc-ui you asked for, boss."""

    response: Annotated[DocUIResponse, IOAttrs('r')]


@ioprepped
@dataclass
class AnalyticsEventMessage(Message):
    """Have a nice analytics event!"""

    event: Annotated[AnalyticsEvent, IOAttrs('e')]


@ioprepped
@dataclass
class AuthRequestMessage(Message):
    """Request access to a server for a current account."""

    global_app_instance_uuid: Annotated[str, IOAttrs('a')]

    @override
    @classmethod
    def get_response_types(cls) -> list[type[Response] | None]:
        return [AuthRequestResponse]


@ioprepped
@dataclass
class AuthRequestResponse(Response):
    """Here's that access ya asked for boss."""

    error: Annotated[str | None, IOAttrs('e')]
    token: Annotated[str | None, IOAttrs('t')]


@ioprepped
@dataclass
class TransientAPIKeyRequest(Message):
    """Request a transient API key for the currently signed-in account."""

    @override
    @classmethod
    def get_response_types(cls) -> list[type[Response] | None]:
        return [TransientAPIKeyResponse]


@ioprepped
@dataclass
class TransientAPIKeyResponse(Response):
    """Response to a transient API key request."""

    class Error(Enum):
        """Failure modes."""

        INTERNAL_ERROR = 'ie'
        KEY_LIMIT_REACHED = 'klr'

    key: Annotated[str | None, IOAttrs('k')]
    error: Annotated[Error | None, IOAttrs('e')]
