import json
import logging
import time
from typing import Optional

import requests
import websocket
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

from py_incharge.utils import find_element_through_shadow


class InCharge:
    AZURE_BASE_URL = "https://businessspecificapimanglobal.azure-api.net"

    TICKET_URL = f"{AZURE_BASE_URL}/remote-commands/editor/tickets"
    COMMAND_ID_URL = (
        f"{AZURE_BASE_URL}/remote-commands/publicCommands?stationName={{station_name}}"
    )
    WEBSOCKET_URL = (
        "wss://emobility-cloud.vattenfall.com/remote-commands/command-execution"
    )

    def __init__(self, email: str, password: str, subscription_key: str):
        self.email = email
        self.password = password
        self.subscription_key = subscription_key

        self.bearer_token: Optional[str] = None

    def login(self):
        """
        Login and retrieve bearer token. The bearer token is a token stored by the application in
        the session storage after a successful login. It is used to authenticate API requests
        and websocket connections.

        This method uses Selenium to automate the login process by filling in the email and password fields
        on the login page, submitting the form, and then retrieving the token from the session storage.

        Note: This method requires the Chrome WebDriver to be installed and available in the system PATH.
        """

        logging.info("Starting the login process to retrieve the bearer token...")

        options = webdriver.ChromeOptions()
        options.add_argument("--headless")
        options.add_argument("--start-maximized")

        logging.info("Waiting for the login page to load...")
        driver = webdriver.Chrome(options=options)
        driver.get("https://myincharge.vattenfall.com/")

        try:
            logging.info("Filling in email and password...")
            email_input = find_element_through_shadow(
                driver,
                hosts_css_chain=["ic-input[formcontrolname='username']"],
                leaf_css="input",
            )
            email_input.send_keys(self.email)
            time.sleep(0.1)

            password_input = find_element_through_shadow(
                driver,
                hosts_css_chain=["ic-input[formcontrolname='password']"],
                leaf_css="input",
            )
            password_input.send_keys(self.password)
            time.sleep(0.2)

            logging.info("Submitting the login form...")
            password_input.send_keys(Keys.RETURN)
            time.sleep(3)

            logging.info("Waiting for the page to load after login...")
            for _ in range(10):
                token = driver.execute_script(
                    "return window.sessionStorage.getItem('auth_token');"
                )
                if token:
                    logging.info("Bearer token found in session storage.")
                    self.bearer_token = token
                    return

                time.sleep(1)

            driver.quit()

            raise Exception("Token not found in session storage.")
        finally:
            driver.quit()
            logging.info("Login successful, bearer token obtained")

    def get_ticket(self) -> str:
        """
        Before starting a remote transaction, a ticket ID must be obtained.
        This ticket is used to authenticate the websocket connection and is required
        to start a remote transaction. This function sends a POST request to the
        InCharge API to obtain a ticket id.
        """
        if not self.bearer_token:
            raise ValueError("Must login first before getting ticket")

        response = requests.post(
            InCharge.TICKET_URL,
            headers={
                "Authorization": f"Bearer {self.bearer_token}",
                "Ocp-Apim-Subscription-Key": self.subscription_key,
                "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:139.0) Gecko/20100101 Firefox/139.0",
            },
            json={},
        )
        if response.status_code == 200:
            print("Ticket request failed:", response.status_code, response.text)
            raise ValueError("Failed to get ticket from API")

        return response.text.strip().strip('"')

    def get_remote_start_command_id(self, station_name: str) -> str:
        """
        Nobody knows why, but the command ID is not static and must be fetched
        from the InCharge API every time before starting a remote transaction.
        This function sends a GET request to the InCharge API to retrieve the command id
        for the remote start transaction command for the specified station.
        """
        if not self.bearer_token:
            raise ValueError("Must login first before getting command ID")

        response = requests.get(
            InCharge.COMMAND_ID_URL.format(station_name=station_name),
            headers={
                "Authorization": f"Bearer {self.bearer_token}",
                "Ocp-Apim-Subscription-Key": self.subscription_key,
                "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:139.0) Gecko/20100101 Firefox/139.0",
            },
        )
        if response.status_code != 200:
            print("Command ID request failed:", response.status_code, response.text)
            raise ValueError("Failed to get command ID from API")

        for command in response.json():
            if command["details"]["name"] == "Remote start transaction":
                return command["commandId"]

        raise ValueError("Remote start transaction command not found")

    def start_remote_transaction(
        self, station_name: str, rfid: str, connector_id: int = 1
    ):
        """
        In short: this method will turn on your EV charger.

        This method starts a remote transaction on the specified station using the provided RFID and connector ID.
        It first retrieves the command ID for the remote start transaction, then obtains a ticket ID,
        and finally establishes a websocket connection to send the remote start command.
        """
        if not self.bearer_token:
            raise ValueError("Must login first before starting transaction")

        logging.info("Starting remote transaction...")
        command_id = self.get_remote_start_command_id(station_name)
        ticket_id = self.get_ticket()

        ws = websocket.create_connection(InCharge.WEBSOCKET_URL)

        ticket_auth_msg = {"type": "TICKET_AUTH", "id": ticket_id}
        ws.send(json.dumps(ticket_auth_msg))
        logging.info(f"Sent TICKET_AUTH: {ticket_auth_msg}")

        response = ws.recv()
        logging.info(f"Received: {response}")

        time.sleep(1)

        custom_command_msg = {
            "type": "CUSTOM_COMMAND",
            "commandId": command_id,
            "stations": [station_name],
            "parameters": {"connectorId": connector_id, "idTag": rfid},
        }
        ws.send(json.dumps(custom_command_msg))
        logging.info(f"Sent CUSTOM_COMMAND: {custom_command_msg}")

        while True:
            msg = ws.recv()
            logging.info(f"Received: {msg}")
            msg_json = json.loads(msg)
            if msg_json.get("type") == "RESPONSE":
                payload = json.loads(msg_json.get("payload", "{}"))
                if payload.get("status") == "Accepted":
                    logging.info("Remote start accepted.")
                    return True
                elif payload.get("status") == "Rejected":
                    logging.error("Remote start rejected.")
                    return False
