import json
import logging
import zipfile

import requests

from pathlib import Path
from typing import (
    Any,
    Dict,
    Optional,
)

from store_config.utils import (
    jsons_dir,
    singleton,
    store_configs_path,
    storecfg_bundles_path,
)
from datetime import (
    datetime,
    date,
)

logger = logging.getLogger(__name__)


@singleton
class StoreConfigurationRepository:
    def __init__(
        self,
        configs,
    ) -> None:

        self.configs = configs

    def get_current_configuration(
        self,
    ) -> Dict:

        try:
            store_config = json.loads(self._read_current_configuration())
            pos_configs = self._get_pos_configs(store_config=store_config)
            jsons_content = self._get_jsons_content(jsons_dir)
            merged_jsons = {
                **store_config,
                **self.configs,
                **pos_configs,
                **jsons_content,
            }

            return merged_jsons

        except requests.exceptions.RequestException:
            logger.exception("Error while updating store configuration file")

            current_config = self._read_current_configuration()
            return json.loads(current_config)

    def update_store_configuration(self) -> bool:
        try:
            download_url = self._consult_external_api()
            zip_response = requests.get(download_url)
            zip_response.raise_for_status()
            self._update_config_file_from_zip(zip_response.content)
            return True

        except (Exception,):
            logger.exception("Failed do update config file")

        return False

    def _consult_external_api(
        self,
    ) -> str:

        store_id = self.configs.get("Store.Id")
        host = self.configs.get("BackOffice.ServerURL")
        url = f"{host}/pump/storeBundle/{store_id}"
        headers = {
            "x-api-key": self.configs.get("BackOffice.ApiKey"),
        }
        response = requests.get(url, headers=headers, allow_redirects=True)
        response.raise_for_status()
        download_url = response.text.strip().strip('"')
        return download_url

    def _update_config_file_from_zip(
        self,
        zip_content: str,
    ) -> Optional[dict]:

        try:
            self._extract_zip_config(zip_content)
            content = self._read_store_config_file()
            config = json.loads(content)
            config["lastStoreConfigUpdate"] = datetime.now().isoformat()
            self._write_store_config_file(config)
            return config

        except (Exception,):
            logger.exception("Error while saving store configuration file")

    @staticmethod
    def _extract_zip_config(
        zip_content: str,
    ) -> None:

        storecfg_bundles_path.mkdir(parents=True, exist_ok=True)
        zip_path = storecfg_bundles_path / "storeBundle.zip"
        with open(zip_path, "wb") as f:
            f.write(zip_content)

        with zipfile.ZipFile(zip_path, "r") as zip_ref:
            zip_ref.extractall(storecfg_bundles_path)

        zip_path.unlink(missing_ok=True)

    @staticmethod
    def _get_jsons_content(
        dir_path: Path,
    ) -> Dict[str, Any]:

        if not dir_path.is_dir():
            logger.info("No jsons directory found")
            return {}

        content = {}
        for file in dir_path.glob("*.json"):
            try:
                with file.open(encoding="utf-8") as f:
                    file_name = file.name.split(".json")[0]
                    file_content = json.load(f)
                    if file_name == "e2e":
                        content.update(file_content)
                    else:
                        content[file_name] = file_content

            except (Exception,):
                logger.exception(f"Failed to read {file.name}")

        return content

    def _read_current_configuration(self) -> str:

        try:
            if not store_configs_path.exists():
                self.update_store_configuration()

            content = self._read_store_config_file()
            if "pos_operator_level" not in json.loads(content):
                self.update_store_configuration()
                content = self._read_store_config_file()

            return content
        except (Exception,):
            logger.exception("Error while reading store configuration file")

        return "{}"

    @staticmethod
    def _read_store_config_file() -> str:
        with open(store_configs_path, 'r', encoding="utf-8") as file:
            return file.read()

    @staticmethod
    def _write_store_config_file(content: dict) -> None:
        try:
            with open(store_configs_path, "w") as file:
                file.write(json.dumps(content))

        except (Exception,):
            logger.exception("Failed to update store configuration file")

    def _get_pos_configs(
        self,
        store_config: Dict[str, str],
    ) -> Dict:
        try:
            pos_configs_json = self._download_from_url(
                url=self.configs.get(
                    "Store.POSConfigsJsonUrl",
                    "https://files.boh.e-deploy.com.br/POSConfigs.json"
                )
            ).content

            return self._update_fiscal_configs(
                pos_configs=json.loads(pos_configs_json.decode()),
                store_config=store_config,
            )
        except (Exception,):
            logger.exception("Error while downloading POSConfigs.json")
            return {}

    @staticmethod
    def _download_from_url(
        url: str,
    ) -> requests.Response:

        return requests.get(url, allow_redirects=True)

    def _update_fiscal_configs(
        self,
        pos_configs: dict,
        store_config: Dict[str, str],
    ) -> dict:

        fiscal_config = pos_configs.get("posConfigs", {}).get("fiscal")
        self._create_nfe_model_55_enabled(
            fiscal_config=fiscal_config,
            pos_configs=pos_configs,
            store_config=store_config,
        )
        return pos_configs

    def _create_nfe_model_55_enabled(
        self,
        fiscal_config: dict,
        pos_configs: dict,
        store_config: Dict[str, str],
    ) -> None:

        nfe_model_55_enabled = store_config.get("store", {}).get("nfe_model_55_enabled", False)
        pos_configs["posConfigs"]["fiscal"]["nfeModel55Enabled"] = nfe_model_55_enabled
        if nfe_model_55_enabled:
            return

        if fiscal_config is None:
            return

        activation_date = fiscal_config.get("nfeModel55Date", None)
        if activation_date is None:
            return

        pos_configs["posConfigs"]["fiscal"]["nfeModel55Enabled"] = self.verify_date(date_to_activate=activation_date)

    @staticmethod
    def verify_date(
        date_to_activate: Optional[str],
    ) -> bool:

        try:
            date_to_activate = datetime.strptime(date_to_activate, "%Y-%m-%d").date()
            today = date.today()
            return today >= date_to_activate

        except (Exception,):
            return False
