from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import List

import yaml
from yaml import CLoader as Loader, CDumper as Dumper
import time


from .util import safe_copy, safe_write_text


class FileVersion:
    def __init__(
            self,
            date: datetime = None,
            filename: str = None,
            author: str = None,
            is_approve: bool = None,
            is_backup: bool = None,
            dir_path: Path = None,
            st_size: int = None,
            cmf_deleted: bool = False,
            version: int = 0,
            md5sum: str = ''
    ):
        # В старых версиях нет оффсета, считаем что там был utc
        if not date.tzinfo:
            date = date.replace(tzinfo=timezone.utc)
        self.date = date
        self.filename = filename
        self.author = author
        self.is_approve = is_approve
        self.is_backup = is_backup
        self.dir_path = dir_path
        self.st_size = st_size
        self.cmf_deleted = cmf_deleted
        self.version = version
        self.md5sum = md5sum

    dump_fieldnames = ['author', 'date', 'filename', 'is_approve', 'is_backup', 'cmf_deleted', 'st_size', 'version', 'md5sum']

    def to_pyobj(self):
        return {k: v for k, v in vars(self).items() if k in self.dump_fieldnames}

    @property
    def path(self):
        return self.dir_path / self.filename


class FileVersions:
    def __init__(self, file_path: Path = None, versions: List[FileVersion] = None):
        if not versions:
            versions = []
        self.versions = versions
        self.file_path = file_path
        
    @property
    def meta_path(self) -> Path:
        return Path(str(self.file_path) + ".meta")

    @property
    def version_file_path(self) -> Path:
        return self.meta_path / "versions.yaml"

    @property
    def versions_dir_path(self) -> Path:
        return self.meta_path / "versions"

    @classmethod
    def for_file_path(cls, file_path: Path) -> "FileVersions":
        fvs = cls(file_path=file_path)

        fvs.version_file_path.touch(exist_ok=True)
        fvs.versions_dir_path.mkdir(exist_ok=True)

        versions = []

        versions_file_data = fvs.version_file_path.read_bytes()

        if versions_file_data:
            st = time.time()
            # safe_load приводил к тормозам
            # TODO1: вернуть safe_load, когда сделаем очистку versions-файла
            for v in yaml.load(versions_file_data, Loader=Loader):
                if v.get('cmf_deleted'):
                    continue
                versions.append(FileVersion(**v, dir_path=fvs.versions_dir_path))
            endt = time.time() - st
            if endt > 0.3:
                print(f'Call yaml.safe_load on file {file_path} run {endt} sec')
        versions.sort(key=lambda v: v.version)
        fvs.versions = versions
        return fvs

    def last_version_date(self):
        if not self.versions:
            return None

        return max(i.date for i in self.versions if not i.is_approve)
    
    def last_version(self):
        if not self.versions:
            return None
        return sorted(self.versions, key=lambda x: x.version, reverse=True)[0]
    
    @staticmethod
    def get_filename(name: str) -> str:
        from . import cmf_hashlib
        import re
        import math
        import mimetypes
        rus_simbol = re.findall(r'[А-Яа-я]', name)
        os_max_length = 127
        file_name_length = len(name) - len(rus_simbol) + len(rus_simbol) * 2
        if file_name_length > os_max_length:
            ext = ''
            mimetype, encoding = mimetypes.guess_type(name)
            if mimetype:
                ext = name.split('.')[-1]
            slice_length = math.floor(os_max_length/2)
            name = f'{name[:slice_length - (5 + len(ext))].strip()}-{cmf_hashlib.short_str_enc(name, 4)}'
            if ext:
                name = f'{name}.{ext}'
            print(f'Слишком длинное название. {file_name_length} > {os_max_length}. Меняем на {name}')
        return name

    def backup(self, **kwargs):
        date = datetime.now(timezone.utc).replace(microsecond=0)
        filename = self.get_filename(date.strftime("%Y%m%d%H%M%S") + '-' + self.file_path.name)
        new_version = FileVersion(
            date=date,
            filename=filename,
            author=kwargs.get("author", None),
            is_approve=False,
            is_backup=True,
            dir_path=self.versions_dir_path,
            st_size= self.file_path.stat().st_size
        )
        # https://bcrm.carbonsoft.ru/desk/cards?obj=Task:TEM-1625015582#na-eve-ne-skachivayutsya-nekotorye-fajly
        md5_path = self.meta_path.joinpath('md5')
        if md5_path.exists():
            new_version.md5sum = md5_path.read_text()
        # проверяем изменился ли файл
        if self.versions and self.versions[-1].st_size == new_version.st_size and self.versions[-1].md5sum == new_version.md5sum:
            return
        safe_copy(self.file_path, new_version.path)
        new_version.st_size = new_version.path.stat().st_size
        if not self.versions:
            new_version.version = 1
        else:
            new_version.version = self.versions[-1].version + 1
        self.versions.append(new_version)
        self.dump_versions_file()

    def backup_smart(self, **kwargs):
        last_version = self.last_version()
        if last_version and last_version.date and (datetime.now(timezone.utc) - last_version.date < timedelta(minutes=30)):
            md5_path = self.meta_path.joinpath('md5')
            if md5_path.exists():
                md5sum = md5_path.read_text()
            st_size= self.file_path.stat().st_size
            # проверяем изменился ли файл
            if last_version.st_size == st_size and last_version.md5sum == md5sum:
                return
            # последнюю версию заменить на новую
            self.remove(last_version)
        self.backup(**kwargs)

    def dump_versions_file(self):
        pyobj = [i.to_pyobj() for i in self.versions]
        data = yaml.safe_dump(pyobj, allow_unicode=True)
        safe_write_text(data, self.version_file_path)

    def restore(self, version: FileVersion):
        src = version.path
        dst = self.file_path
        safe_copy(src, dst)

    def remove(self, version: FileVersion):
        """Фейковое удаление, просто не загружаем в список"""
        version.cmf_deleted = True
        self.dump_versions_file()
        self.versions.remove(version)
