from pathlib import Path
from enum import Enum
import os
import logging

import jwt
import boto3

from .rfile import RFile
from .io_s3 import IOS3
from .io_fs import IOFS


logging.getLogger('boto3').setLevel(logging.ERROR)
logging.getLogger('botocore').setLevel(logging.ERROR)
logging.getLogger('s3transfer').setLevel(logging.ERROR)
logging.getLogger('urllib3').setLevel(logging.ERROR)


class IOState(Enum):
    PRIMARY = "PRIMARY"
    SECONDARY = "SECONDARY"


class RDisk:
    _s3_conn = None

    def __init__(self, token, uri, jwt_key, is_obj_api=False, is_admin=False, login=None, config=None):
        self.uri = uri
        
        # API для CmfAttachments
        self.is_obj_api = is_obj_api
        # Прозрачная передача флага режима администратора
        self.is_admin = is_admin

        if token and login:
            raise Exception('Несовместимые опции: token и login')

        # TODO2: токен все еще работает в webdav, хотя он там уже неполноценный, т.к. мы scope подрезали
        # так что мб в будущем переделать код на проверку login, вместо token. Token использовать только для получения
        # login
        if token:
            self.token = jwt.decode(token, jwt_key, algorithms=['RS256', 'HS256'])
            self.login = None
        else:
            self.token = None
            self.login = login

        primary_io, self.primary_base_path = self._init_io(self.uri, config)

        fallback_config = config.get("fallback")
        if fallback_config:
            secondary_io, self.secondary_base_path = self._init_io(fallback_config["uri"], fallback_config)
        else:
            secondary_io, self.secondary_base_path = None, None

        self._io = IOManager(primary_io, secondary_io)
        self.root = self.get_rfile("/")
        self.cur_rfile = None

    def _init_io(self, uri, config):
        if uri.startswith("s3://"):
            try:
                if type(self)._s3_conn is None:
                    type(self)._s3_conn = boto3.client(
                        's3',
                        endpoint_url=config["endpoint_url"],
                        aws_access_key_id=config["aws_access_key_id"],
                        aws_secret_access_key=config["aws_secret_access_key"],
                        aws_session_token=None,
                        config=boto3.session.Config(
                            signature_version="s3v4",
                            max_pool_connections=50,
                            tcp_keepalive=True,
                            retries={
                                "mode": "adaptive",
                                'max_attempts': 1
                                }
                            ),
                        verify=False
                    )

                io = IOS3(type(self)._s3_conn, config["bucket_name"])
            except:
                io = None
            finally:
                base_path = uri.split("s3://")[1]
        else:
            io = IOFS()
            base_path = uri.split("file://")[1]
        return io, base_path

    def create_rfile_from_template(self, dirname, filename):
        ext = filename.split('.')[-1]
        template = Path(__file__).parent / "templates" / f"template.{ext}"
        if not template.exists():
            raise ValueError(f"Нет шаблона для {ext}")
        dir = self.get_rfile(dirname)
        child = dir.add_child(filename, False)
        child.write(template.read_bytes())
        return child

    def get_rfile(self, id, io=None):
        return RFile(id=id, _rdisk=self, cur_io=io)

    def get_rfile_by_path(self, path: str):
        id = path.split(self.base_path)[1]
        if not id.startswith("/"):
            id = "/" + id
        return RFile(id=id, _rdisk=self)

    @property
    def base_path(self):
        if self.cur_rfile.cur_io is IOState.PRIMARY:
            return self.primary_base_path
        if self.cur_rfile.cur_io is IOState.SECONDARY:
            return self.secondary_base_path
        
        raise AttributeError("Unable to determine primary IO")

    @property
    def io(self):        
        self._io.switch_to(self.cur_rfile.cur_io)
        return self._io
    
    def __get__(self, instance, object=None):
        if isinstance(instance, RFile) and instance.cur_io is None:
            primary_exists = self._exists(self._io.primary, self.primary_base_path, instance)
            secondary_exists = self._exists(self._io.secondary, self.secondary_base_path, instance)

            if primary_exists is None and secondary_exists is None:
                raise ValueError("No primary IO initialized")
            elif primary_exists is False and secondary_exists is False:
                instance.cur_io = IOState.PRIMARY
            elif secondary_exists is None or primary_exists:
                instance.cur_io = IOState.PRIMARY
            elif primary_exists is None or secondary_exists:
                instance.cur_io = IOState.SECONDARY
            else:
                raise AttributeError("Unable to determine primary IO")

        self.cur_rfile = instance
        return self
    
    def _exists(self, io, base_path, rfile):
        if io is not None:
            path = os.path.join(base_path, *rfile.id.split("/"))
            try:
                exists = io.io_exists(path)
            except Exception as e:
                logging.error(f"IO Exception: {e}")
                exists = None
        else:
            exists = None
        return exists


class IOManager:
    def __init__(self, primary, secondary=None):
        self.primary = primary
        self.secondary = secondary

        self.current = self.primary

    def __getattr__(self, name):
        return getattr(self.current, name)
    
    def switch_to(self, target):
        if target is IOState.PRIMARY:
            self.current = self.primary
            return
        
        if target is IOState.SECONDARY:
            self.current = self.secondary
            return

        raise AttributeError("Unable to determine primary IO")