import logging
import requests

import voluptuous as vol
import traceback
import datetime as dt
from datetime import timedelta

from .APSystemsSocket import APSystemsSocket, APSystemsInvalidData
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.helpers.entity import Entity
from homeassistant import config_entries, exceptions
from homeassistant.helpers import device_registry as dr
from homeassistant.components.persistent_notification import (
    create as create_persistent_notification
    )
from homeassistant.helpers.update_coordinator import (
    CoordinatorEntity,
    DataUpdateCoordinator,
    UpdateFailed,
    )
from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)
PLATFORMS = [ "sensor", "binary_sensor", "switch" ]

class WiFiSet():
    ipaddr = ""
    ssid = ""
    wpa = ""
    cache = 3
WiFiSet = WiFiSet()

# handle all the communications with the ECUR class and deal with our need for caching, etc
class ECUR():
    def __init__(self, ipaddr, ssid, wpa, cache, nographs):
        self.ecu = APSystemsSocket(ipaddr, nographs)
        self.cache_count = 0
        self.data_from_cache = False
        self.querying = True
        self.inverters_online = True
        self.ecu_restarting = False
        self.cached_data = {}
        WiFiSet.ipaddr = ipaddr
        WiFiSet.ssid = ssid
        WiFiSet.wpa = wpa
        WiFiSet.cache = cache

    def stop_query(self):
        self.querying = False

    def start_query(self):
        self.querying = True
        
    def inverters_off(self):
        headers = {'X-Requested-With': 'XMLHttpRequest'}
        url = 'http://'+ str(WiFiSet.ipaddr) + '/index.php/configuration/set_switch_all_off'
        try:
            get_url = requests.post(url, headers=headers)
            self.inverters_online = False
            _LOGGER.debug(f"Response from ECU on switching the inverters off: {str(get_url.status_code)}")
        except Exception as err:
            _LOGGER.warning(f"Attempt to switch inverters off failed with error: {err} (This switch is only compatible with ECU-R pro and ECU-C type ECU's)")

    def inverters_on(self):
        headers = {'X-Requested-With': 'XMLHttpRequest'}
        url = 'http://'+ str(WiFiSet.ipaddr) + '/index.php/configuration/set_switch_all_on'
        try:
            get_url = requests.post(url, headers=headers)
            self.inverters_online = True
            _LOGGER.debug(f"Response from ECU on switching the inverters on: {str(get_url.status_code)}")
        except Exception as err:
            _LOGGER.warning(f"Attempt to switch inverters on failed with error: {err} (This switch is only compatible with ECU-R pro and ECU-C type ECU's)")

    def use_cached_data(self, msg):
        # we got invalid data, so we need to pull from cache
        self.error_msg = msg
        self.cache_count += 1
        self.data_from_cache = True

        if self.cache_count == WiFiSet.cache:
            _LOGGER.warning(f"Communication with the ECU failed after {WiFiSet.cache} repeated attempts.")
            data = {'SSID': WiFiSet.ssid, 'channel': 0, 'method': 2, 'psk_wep': '', 'psk_wpa': WiFiSet.wpa}
            _LOGGER.debug(f"Data sent with URL: {data}")
            # Determine ECU type to decide ECU restart (for ECU-C and ECU-R with sunspec only)
            if (self.cached_data.get("ecu_id", None)[0:3] == "215") or (self.cached_data.get("ecu_id", None)[0:4] == "2162"):
                url = 'http://' + str(WiFiSet.ipaddr) + '/index.php/management/set_wlan_ap'
                headers = {'X-Requested-With': 'XMLHttpRequest'}
                try:
                    get_url = requests.post(url, headers=headers, data=data)
                    _LOGGER.debug(f"Response from ECU on restart: {str(get_url.status_code)}")
                    self.ecu_restarting = True
                except Exception as err:
                    _LOGGER.warning(f"Attempt to restart ECU failed with error: {err}. Querying is stopped automatically.")
                    self.querying = False
            else:
                # Older ECU-R models starting with 2160
                _LOGGER.warning("Try manually power cycling the ECU. Querying is stopped automatically, turn switch back on after restart of ECU.")
                self.querying = False
            
        if self.cached_data.get("ecu_id", None) == None:
            _LOGGER.debug(f"Cached data {self.cached_data}")
            raise UpdateFailed(f"Unable to get correct data from ECU, and no cached data. See log for details, and try power cycling the ECU.")
        return self.cached_data

    def update(self):
        data = {}
        # if we aren't actively quering data, pull data form the cache
        # this is so we can stop querying after sunset
        if not self.querying:
            _LOGGER.debug("Not querying ECU due to query=False")
            data = self.cached_data
            self.data_from_cache = True
            data["data_from_cache"] = self.data_from_cache
            data["querying"] = self.querying
            return self.cached_data

        _LOGGER.debug("Querying ECU...")
        try:
            data = self.ecu.query_ecu()
            _LOGGER.debug("Got data from ECU")

            # we got good results, so we store it and set flags about our cache state
            if data["ecu_id"] != None:
                self.cached_data = data
                self.cache_count = 0
                self.data_from_cache = False
                self.ecu_restarting = False
                self.error_message = ""
            else:
                msg = f"Using cached data from last successful communication from ECU. Error: no ecu_id returned"
                _LOGGER.warning(msg)
                data = self.use_cached_data(msg)

        except APSystemsInvalidData as err:
            msg = f"Using cached data from last successful communication from ECU. Invalid data error: {err}"
            if str(err) != 'timed out':
                _LOGGER.warning(msg)
            data = self.use_cached_data(msg)

        except Exception as err:
            msg = f"Using cached data from last successful communication from ECU. Exception error: {err}"
            _LOGGER.warning(msg)
            data = self.use_cached_data(msg)

        data["data_from_cache"] = self.data_from_cache
        data["querying"] = self.querying
        data["restart_ecu"] = self.ecu_restarting
        _LOGGER.debug(f"Returning {data}")
        if data.get("ecu_id", None) == None:
            raise UpdateFailed(f"Somehow data doesn't contain a valid ecu_id")
        return data

async def update_listener(hass, config):
    # Handle options update being triggered by config entry options updates
    _LOGGER.debug(f"Configuration updated: {config.as_dict()}")
    ecu = ECUR(config.data["host"],
               config.data["SSID"],
               config.data["WPA-PSK"],
               config.data["CACHE"],
               config.data["stop_graphs"]
              )

async def async_setup_entry(hass, config):
    # Setup the APsystems platform """
    hass.data.setdefault(DOMAIN, {})
    host = config.data["host"]
    interval = timedelta(seconds=config.data["scan_interval"])
    # Defaults for new parameters that might not have been set yet from previous integration versions
    cache = config.data.get("CACHE", 5)
    ssid = config.data.get("SSID", "ECU-WiFi_SSID")
    wpa = config.data.get("WPA-PSK", "myWiFipassword")
    nographs = config.data.get("stop_graphs", False)
    ecu = ECUR(host, ssid, wpa, cache, nographs)
    

    async def do_ecu_update():
        return await hass.async_add_executor_job(ecu.update)

    coordinator = DataUpdateCoordinator(
            hass,
            _LOGGER,
            name=DOMAIN,
            update_method=do_ecu_update,
            update_interval=interval,
    )

    hass.data[DOMAIN] = {
        "ecu" : ecu,
        "coordinator" : coordinator
    }
    await coordinator.async_config_entry_first_refresh()

    device_registry = dr.async_get(hass)

    device_registry.async_get_or_create(
        config_entry_id=config.entry_id,
        identifiers={(DOMAIN, f"ecu_{ecu.ecu.ecu_id}")},
        manufacturer="APSystems",
        suggested_area="Roof",
        name=f"ECU {ecu.ecu.ecu_id}",
        model=ecu.ecu.firmware,
        sw_version=ecu.ecu.firmware,
    )

    inverters = coordinator.data.get("inverters", {})
    for uid,inv_data in inverters.items():
        model = inv_data.get("model", "Inverter")
        device_registry.async_get_or_create(
            config_entry_id=config.entry_id,
            identifiers={(DOMAIN, f"inverter_{uid}")},
            manufacturer="APSystems",
            suggested_area="Roof",
            name=f"Inverter {uid}",
            model=inv_data.get("model")
        )
    await hass.config_entries.async_forward_entry_setups(config, PLATFORMS)
    config.async_on_unload(config.add_update_listener(update_listener))
    return True

async def async_remove_config_entry_device(hass, config, device_entry) -> bool:
    if device_entry is not None:
        # Notify the user that the device has been removed
        create_persistent_notification(
            hass,
            title="Important notification",
            message=f"The following device was removed from the system: {device_entry}"
        )
        return True
    else:
        return False

async def async_unload_entry(hass, config):
    unload_ok = await hass.config_entries.async_unload_platforms(config, PLATFORMS)
    coordinator = hass.data[DOMAIN].get("coordinator")
    ecu = hass.data[DOMAIN].get("ecu")
    ecu.stop_query()
    if unload_ok:
        hass.data[DOMAIN].pop(config.entry_id)
    return unload_ok
