# Copyright (C) 2015-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/

import sqlite3
import s3fs.core

NOT_GIVEN = -917


EVICTION_POLICY = {
    "none": {
        "evict": None,
    },
    "least-recently-stored": {
        "evict": "SELECT {fields} FROM archive_cache ORDER BY store_time",
    },
    "least-recently-used": {
        "evict": "SELECT {fields} FROM archive_cache ORDER BY access_time",
    },
    "least-frequently-used": {
        "evict": "SELECT {fields} FROM archive_cache ORDER BY access_count",
    },
}


def archive_iterator(_reader, block_size: int = 4096 * 512):
    # 4096 * 64 = 64KB
    while 1:
        data = _reader.read(block_size)
        if not data:
            break
        yield data


def format_size(size):
    # Convert size in bytes to a human-readable format (e.g., KB, MB, GB)
    for unit in ["B", "KB", "MB", "GB", "TB"]:
        if size < 1024:
            return f"{size:.2f} {unit}"
        size /= 1024


class StatsDB:
    def __init__(self):
        self.connection = sqlite3.connect(":memory:")
        self._init_db()

    def _init_db(self):
        qry = """
            CREATE TABLE IF NOT EXISTS archive_cache (
             rowid INTEGER PRIMARY KEY,
             key_file TEXT,
             key_file_path TEXT,
             archive_key TEXT,
             archive_path TEXT,
             store_time REAL,
             access_time REAL,
             access_count INTEGER DEFAULT 0,
             size INTEGER DEFAULT 0
             )
        """

        self.sql(qry)
        self.connection.commit()

    @property
    def sql(self):
        return self.connection.execute

    def bulk_insert(self, rows):
        qry = """
            INSERT INTO archive_cache (
             rowid,
             key_file,
             key_file_path,
             archive_key,
             archive_path,
             store_time,
             access_time,
             access_count,
             size
            )
            VALUES (
            ?, ?, ?, ?, ?, ?, ?, ?, ?
            )
        """
        cursor = self.connection.cursor()
        cursor.executemany(qry, rows)
        self.connection.commit()

    def get_total_size(self):
        qry = "SELECT COALESCE(SUM(size), 0) FROM archive_cache"
        ((total_size,),) = self.sql(qry).fetchall()
        return total_size

    def get_sorted_keys(self, select_policy):
        select_policy_qry = select_policy.format(fields="key_file, archive_key, size")
        return self.sql(select_policy_qry).fetchall()


class ShardFileReader:
    def __init__(self, file_like_reader):
        self._file_like_reader = file_like_reader

    def __getattr__(self, item):
        if isinstance(self._file_like_reader, s3fs.core.S3File):
            match item:
                case "name":
                    # S3 FileWrapper doesn't support name attribute, and we use it
                    return self._file_like_reader.full_name
                case _:
                    return getattr(self._file_like_reader, item)
        else:
            return getattr(self._file_like_reader, item)

    def __repr__(self):
        return f"<{self.__class__.__name__}={self._file_like_reader}>"
