######################################################################
#
# File: b2sdk/v1/file_version.py
#
# Copyright 2021 Backblaze Inc. All Rights Reserved.
#
# License https://www.backblaze.com/using_b2_code.html
#
######################################################################
from __future__ import annotations

from contextlib import suppress
import datetime
import functools

from b2sdk import v2
from . import api as v1api


# Override to retain legacy class name, __init__ signature, slots
# and old formatting methods
# and to omit 'api' property when doing __eq__ and __repr__
# and to make get_fresh_state return proper objects, even though v1.B2Api.get_file_info returns dicts
class FileVersionInfo(v2.FileVersion):
    __slots__ = ['_api']

    LS_ENTRY_TEMPLATE = '%83s  %6s  %10s  %8s  %9d  %s'  # order is file_id, action, date, time, size, name

    def __init__(
        self,
        id_,
        file_name,
        size,
        content_type,
        content_sha1,
        file_info,
        upload_timestamp,
        action,
        account_id: str | None = None,
        bucket_id: str | None = None,
        content_md5=None,
        server_side_encryption: v2.EncryptionSetting | None = None,
        file_retention: v2.FileRetentionSetting | None = None,
        legal_hold: v2.LegalHold | None = None,
        api: v1api.B2Api | None = None,
        cache_control: str | None = None,
        **kwargs
    ):
        self.id_ = id_
        self.file_name = file_name
        self.size = size and int(size)
        self.content_type = content_type
        self.content_sha1, self.content_sha1_verified = self._decode_content_sha1(content_sha1)
        self.account_id = account_id
        self.bucket_id = bucket_id
        self.content_md5 = content_md5
        self.file_info = file_info or {}
        self.upload_timestamp = upload_timestamp
        self.action = action
        self.server_side_encryption = server_side_encryption
        self.legal_hold = legal_hold
        self.file_retention = file_retention
        self._api = api
        self.cache_control = cache_control

        # allow common tests to execute without hitting attributeerror

        with suppress(KeyError):
            del kwargs['replication_status']
        self.replication_status = None
        assert not kwargs  # after we get rid of everything we don't support in this apiver, this should be empty

        if v2.SRC_LAST_MODIFIED_MILLIS in self.file_info:
            self.mod_time_millis = int(self.file_info[v2.SRC_LAST_MODIFIED_MILLIS])
        else:
            self.mod_time_millis = self.upload_timestamp

    @property
    def api(self):
        if self._api is None:
            raise ValueError('"api" not set')
        return self._api

    def _all_slots(self):
        all_slots = super()._all_slots()
        all_slots.remove('api')
        return all_slots

    def format_ls_entry(self):
        dt = datetime.datetime.utcfromtimestamp(self.upload_timestamp / 1000)
        date_str = dt.strftime('%Y-%m-%d')
        time_str = dt.strftime('%H:%M:%S')
        size = self.size or 0  # required if self.action == 'hide'
        return self.LS_ENTRY_TEMPLATE % (
            self.id_,
            self.action,
            date_str,
            time_str,
            size,
            self.file_name,
        )

    @classmethod
    def format_folder_ls_entry(cls, name):
        return cls.LS_ENTRY_TEMPLATE % ('-', '-', '-', '-', 0, name)

    def get_fresh_state(self) -> FileVersionInfo:
        """
        Fetch all the information about this file version and return a new FileVersion object.
        This method does NOT change the object it is called on.
        """
        return self.api.file_version_factory.from_api_response(self.api.get_file_info(self.id_))


def file_version_info_from_new_file_version(file_version: v2.FileVersion) -> FileVersionInfo:
    return FileVersionInfo(
        **{
            att_name: getattr(file_version, att_name)
            for att_name in [
                'id_',
                'file_name',
                'size',
                'content_type',
                'content_sha1',
                'file_info',
                'upload_timestamp',
                'action',
                'content_md5',
                'server_side_encryption',
                'legal_hold',
                'file_retention',
                'cache_control',
                'api',
            ]
        }
    )


def translate_single_file_version(func):
    @functools.wraps(func)
    def inner(*a, **kw):
        return file_version_info_from_new_file_version(func(*a, **kw))

    return inner


# override to return old style FileVersionInfo
class FileVersionInfoFactory(v2.FileVersionFactory):

    from_api_response = translate_single_file_version(v2.FileVersionFactory.from_api_response)

    def from_response_headers(self, headers):

        file_info = v2.DownloadVersionFactory.file_info_from_headers(headers)
        return FileVersionInfo(
            api=self.api,
            id_=headers['x-bz-file-id'],
            file_name=headers['x-bz-file-name'],
            size=int(headers['content-length']),
            content_type=headers['content-type'],
            content_sha1=headers['x-bz-content-sha1'],
            file_info=file_info,
            upload_timestamp=int(headers['x-bz-upload-timestamp']),
            action='upload',
            content_md5=None,
            server_side_encryption=v2.EncryptionSettingFactory.from_response_headers(headers),
            file_retention=v2.FileRetentionSetting.from_response_headers(headers),
            legal_hold=v2.LegalHold.from_response_headers(headers),
            cache_control=headers['Cache-control'],
        )


def file_version_info_from_id_and_name(file_id_and_name: v2.FileIdAndName, api: v1api.B2Api):
    return FileVersionInfo(
        id_=file_id_and_name.file_id,
        file_name=file_id_and_name.file_name,
        size=0,
        content_type='unknown',
        content_sha1='none',
        file_info={},
        upload_timestamp=0,
        action='cancel',
        api=api,
    )


def file_version_info_from_download_version(download_version: v2.DownloadVersion):
    return FileVersionInfo(
        id_=download_version.id_,
        file_name=download_version.file_name,
        size=download_version.size,
        content_type=download_version.content_type,
        content_sha1=download_version.content_sha1,
        file_info=download_version.file_info,
        upload_timestamp=download_version.upload_timestamp,
        action='upload',
        content_md5=None,
        server_side_encryption=download_version.server_side_encryption,
        file_retention=download_version.file_retention,
        legal_hold=download_version.legal_hold,
        cache_control=download_version.cache_control,
        api=download_version.api,
    )
