"""

    AUTHOR:

        Khushal P Soonderji

    DATE:

        Monday, 30th Dec., 2024

    OBJECTIVE:

        To handle all trading related behaviour for ICICI's Breeze platform.

    REFERENCES:

        N/A

    DOWNLOADS:

        N/A

"""


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                   IMPORT                                                      ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")

# My async utils:
from utils_v2.string import json
from utils_v2.database.async_mysql_v2 import AsyncMySQL
from utils_v2.database.async_mongo_v2 import AsyncMongo
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache

# Controllers:
from controllers_v2.finstitutions.trading.base import TradingController

# Models:
from models.core.auth_token import CoreAuthTokenModel
from utils_v2.trading.icici_breeze.models.auth_tokens import ICICIBreezeAuthTokens
from models.api.finstitutions.trading.symbols.list import TradingSymbolListRequestData
from models.finstitutions.trading.symbols import TradingSymbolListBrokerResponse, TradingSymbol
from models.finstitutions.trading.oauth import TradingOAuthCallbackResponse

# To work with MongoDB:
from bson.objectid import ObjectId

# To work with datatypes:
from typing import List, Any

# To make HTTP requests:
import httpx
import urllib

# To work with ICICI Breeze's platform:
from breeze_connect import BreezeConnect

# To handle exceptions:
from pydantic import ValidationError


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                           MACROS / ONE-TIME INIT                                              ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# --- Nothing Yet


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                VARIABLES                                                      ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# --- Nothing Yet


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                  FUNCTIONS                                                    ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# --- Nothing Yet


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                   CLASSES                                                     ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


class ICICIBreezeTradingController(TradingController):

    # ┏┓┓      ┓┏
    # ┃ ┃┏┓┏┏  ┃┃┏┓┏┓┏
    # ┗┛┗┗┻┛┛  ┗┛┗┻┛ ┛

    CLIENT_NAME = "iciciBreeze"

    # ┏┓
    # ┃ ┏┓┏┓┏╋┏┓┓┏┏╋┏┓┏┓
    # ┗┛┗┛┛┗┛┗┛ ┗┻┗┗┗┛┛

    def __init__(
            self,
            cache: AsyncRedisCache = None,
            http_client: httpx.AsyncClient = None,
            alert_url: str = None,
            debug: bool = True,
            debug_prefix: str = "ICICI Breeze (C) | ",
            debug_only_errors: bool = True
    ):

        """
        This is the foundational controller for ICICI's Breeze platform.
        :param cache: The object to use for caching results from database calls.
        :param http_client: The HTTP client
        :param debug: Whether, or not, you would like to print debugging messages:
        :param debug_prefix: The prefix to print with the debugging messages.
        :param debug_only_errors: Whether you would like to print only error messages or all messages.
        :return: None.
        """

        # Declare the client:
        this_client = self.CLIENT_NAME

        # Prepare base filter:
        this_filter = {"client": this_client}

        # Invoke the parent's constructor:
        super().__init__(
            cache = cache,
            alert_url = alert_url,
            http_client = http_client,
            base_filter = this_filter,
            debug = debug,
            debug_prefix = debug_prefix,
            debug_only_errors = debug_only_errors
        )

        # Init a variable in a parent:
        self._client = this_client

    # ┏┓   ┓
    # ┣┫┓┏╋┣┓
    # ┛┗┗┻┗┛┗

    @staticmethod
    async def get_authorization_url(
            **kwargs
    ) -> str:

        """
        To generate an authorization URL for this broker.
        :param kwargs: Any no. of things needed by your broker to generate the URL.
        :return: The authorization URL.
        """

        return "https://api.icicidirect.com/apiuser/login?api_key=" + urllib.parse.quote_plus(kwargs["api_key"])

    async def handle_authorization_callback(
            self,
            sql_conn: AsyncMySQL,
            mongo_data_conn: AsyncMongo,
            inbound_data: dict,
            client_user_id: str
    ) -> TradingOAuthCallbackResponse:

        """
        To capture the callback from ICICI Breeze's authorization loop. This happens when the user successfully logs in
        to his account through the login URL.
        :param sql_conn: The database connection to use to perform this activity.
        :param mongo_data_conn: The database connection to use to perform this activity.
        :param inbound_data: The data that came in from the broker. This could be in the JSON body, query params, etc.
        :param client_user_id: How the trading client identifies this user.
        :return: A structured response to capture the process of callback handling.
        """

        # Start by assuming failure:
        response = TradingOAuthCallbackResponse()
        icici_auth_token = None

        # Check if the callback URL was configured properly:
        if not client_user_id:
            response.message = "Your callback URL hasn't been configured properly."
            return response

        # Get the token from the database:
        condition = mongo_data_conn.dict_to_dot_notation({"auth": {"userId": client_user_id}})
        auth_token = await self.get_token_from_filter(
            mongo_data_conn = mongo_data_conn,
            filter_json = condition
        )

        # If not such auth token exists:
        if not auth_token:
            response.message = (
                f"We couldn't find such an integration in our system. "
                "Please add this integration first and then try again."
            )
            return response

        # Get the access token and user information from ICICI Breeze:
        breeze_session_token = inbound_data.get("apisession")
        try:
            breeze = BreezeConnect(api_key = auth_token.auth["apiKey"])
            breeze.generate_session(
                api_secret = auth_token.auth["apiSecret"],
                session_token = breeze_session_token
            )
            session_data = breeze.get_customer_details(api_session = breeze_session_token)
            session_data = session_data.get("Success")
            icici_auth_token = ICICIBreezeAuthTokens(**session_data)
        except Exception as exception:
            response.message = f"Client exception: {exception}"
            response.exception = exception
            return response

        # Ensure that the client user id of the incoming callback and the one given in Zerodha's session data match:
        if icici_auth_token.userId != client_user_id:
            response.message = (
                f"We were expecting authorization for the account '{client_user_id}', "
                f"but ICICI says the authorization was granted for the account '{icici_auth_token.userId}'. "
                "This could be because of a misconfigured callback URL."
            )
            return response

        # Prepare the inputs to save to the database:
        auth_url = await self.get_authorization_url(api_key = auth_token.auth["apiKey"])
        icici_auth_token.sessionToken = breeze_session_token
        auth_token.token = icici_auth_token.model_dump()

        # Save the additional auth info to the database:
        success = await self.set_token(
            sql_conn = sql_conn,
            mongo_data_conn = mongo_data_conn,
            token_key = auth_token.key,
            auth_token = auth_token,
            token_notes = {
                "apiKey": auth_token.auth["apiKey"],
                "authUrl": auth_url
            },
            display_name = icici_auth_token.userId,
            display_picture = icici_auth_token.displayPictureUrl
        )

        # If saving the token fails:
        if not success:
            response.message = "Something went wrong towards the end of the authorization cycle."
            return response

        # Done here:
        response.success = True
        response.message = "Authorization cycle successfully completed."
        return response

    # ┏┳┓     ┓•      ┏┓     ┓   ┓
    #  ┃ ┏┓┏┓┏┫┓┏┓┏┓  ┗┓┓┏┏┳┓┣┓┏┓┃┏
    #  ┻ ┛ ┗┻┗┻┗┛┗┗┫  ┗┛┗┫┛┗┗┗┛┗┛┗┛
    #              ┛     ┛

    async def list_symbols(
            self,
            mongo_data_conn: AsyncMongo,
            auth_token: CoreAuthTokenModel,
            inbound_data: TradingSymbolListRequestData
    ) -> TradingSymbolListBrokerResponse:

        """
        To get the list of tradeable symbols offered by ICICI Breeze.
        :param mongo_data_conn: The database connection to use to perform this activity.
        :param auth_token: The token that has to be used to fetch the data.
        :param inbound_data: The data that came in with the APi call.
        :return: The structured response form the broker.
        """

        raise NotImplementedError


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                               MAIN PROGRAM                                                    ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


if __name__ == "__main__":

    pass
