from __future__ import annotations

import logging
from typing import Dict, Tuple, List

from clarabot_nano_common.common.config.extract.data_dir import DataDirExtractedConfig
from clarabot_nano_common.common.config.extract.solr import SolrExtractedConfig
from clarabot_nano_common.common.solr.rest import create_solr_auth_header, create_nano_query_url, create_nano_update_url
from clarabot_nano_common.utility.path.chroot import ChrootPath
from clarabot_nano_common.utility.path.restrict import PathRestrictionValidator
from clarabot_nano_common.utility.path.sensitive import SensitiveValidationError
from clarabot_nano_common.utility.path.validate import ValidationError

from clarabot_nano.common.config.extract.picoture import PicotureExtractedConfig
from clarabot_nano.common.config.extract.tika import TikaExtractedConfig
from clarabot_nano.common.config.utility import config_get_solr_data_dir

logger = logging.getLogger(__name__)


class IndexerConfig(DataDirExtractedConfig, SolrExtractedConfig, TikaExtractedConfig, PicotureExtractedConfig):
    """
    Configurations relevant to the job of the indexer are extracted from the core-config here.
    """

    def __init__(self, config):
        DataDirExtractedConfig.__init__(self, config)
        SolrExtractedConfig.__init__(self, config)
        TikaExtractedConfig.__init__(self, config)
        PicotureExtractedConfig.__init__(self, config)
        self.drives = set(drive["path"] for drive in config["remote"]["drives"])
        self.restrict_sensitive_path_access = config["local"]["restrict_sensitive_path_access"]
        self.indexed_languages = config["remote"].get("indexed_languages")
        self.indexer_sync_interval = config["remote"].get("indexer_sync_interval")
        self.indexer_remove_lost_count = config["remote"].get("indexer_remove_lost_count")
        self.indexer_force_sync_on_startup = config["remote"]["indexer_force_sync_on_startup"]
        self.indexer_minimum_delay_between_path_sync = config["remote"]["indexer_minimum_delay_between_path_sync"]
        self.indexer_reserved_space = config["remote"].get("indexer_reserved_space") * 1000  # make it KB for use
        self.indexer_ignored_mimes = set(config["remote"]["indexer_ignored_mimes"])
        self.indexer_extract_size_limit = config["remote"]["indexer_extract_size_limit"]
        self.indexer_extract_text_limit = config["remote"]["indexer_extract_text_limit"]
        self.solr_data_dir = config_get_solr_data_dir(config)
        # needs no matches for local config
        self.drive_restriction_validator = PathRestrictionValidator(config["local"]["drive_restriction_paths"])
        # prepare these from the extracted config
        self.solr_auth_header = create_solr_auth_header(self.solr_key) if self.solr_key else ""
        self.solr_query_url = create_nano_query_url(self.solr_port, self.solr_core) if self.solr_port else ""
        self.solr_update_url = create_nano_update_url(self.solr_port, self.solr_core) if self.solr_port else ""
        self.indexer_delay = self.indexer_sync_interval * 3600.0
        self.lost_remove_minimum_delay = self.indexer_delay * self.indexer_remove_lost_count

    def _matches(self, other: IndexerConfig) -> bool:
        return (
            DataDirExtractedConfig._matches(self, other)
            and SolrExtractedConfig._matches(self, other)
            and TikaExtractedConfig._matches(self, other)
            and PicotureExtractedConfig._matches(self, other)
            and self.drives == other.drives
            and self.restrict_sensitive_path_access == other.restrict_sensitive_path_access
            and self.indexed_languages == other.indexed_languages
            and self.indexer_sync_interval == other.indexer_sync_interval
            and self.indexer_remove_lost_count == other.indexer_remove_lost_count
            and self.indexer_force_sync_on_startup == other.indexer_force_sync_on_startup
            and self.indexer_minimum_delay_between_path_sync == other.indexer_minimum_delay_between_path_sync
            and self.indexer_reserved_space == other.indexer_reserved_space
            and self.indexer_ignored_mimes == other.indexer_ignored_mimes
            and self.indexer_extract_size_limit == other.indexer_extract_size_limit
            and self.indexer_extract_text_limit == other.indexer_extract_text_limit
            and self.solr_data_dir == other.solr_data_dir
        )

    def get_drive_chroots(self) -> Tuple[Dict[str, ChrootPath], List[str]]:
        chroots = dict()
        failed_root_paths = set()
        for path in self.drives:
            try:
                chroot = ChrootPath(
                    path,
                    allow_path_cycles=False,
                    restrict_sensitive=self.restrict_sensitive_path_access,
                    extra_root_validators=(self.drive_restriction_validator,),
                )
            except SensitiveValidationError:
                logger.warning("Drive root is denied because of sensitive data: %s", path)
                failed_root_paths.add(path)
                continue
            except (ValidationError, FileNotFoundError, NotADirectoryError):
                logger.warning("Drive root not found or invalid: %s", path)
                failed_root_paths.add(path)
                continue
            except OSError:
                logger.exception("Drive root can't be indexed: %s", path)
                failed_root_paths.add(path)
                continue
            root = chroot.root.as_posix()
            if root not in chroots:
                # Only one root path is used for resolution. The indexing must work if any of them work so omitting
                # others that are not operational is fine. If the active becomes invalid it will trigger a chroot-set
                # rebuild so a valid alternative cn take its place.
                chroots[root] = chroot
            else:
                chroots[root].add_alternate_root_path(path)
        # Keep the roots in a deterministic order to support iterative traversals.
        chroots = dict((root, chroots[root]) for root in sorted(chroots.keys()))
        return chroots, sorted(p for p in failed_root_paths if p not in chroots)
