# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

from typing import Any, List
import pytest


from pyiceberg.io import FileIO
from pyiceberg.io.pyarrow import PyArrowFileIO
from pyiceberg.manifest import (
    DataFile,
    DataFileContent,
    FileFormat,
    ManifestEntry,
    ManifestEntryStatus,
    _manifests,
)


@pytest.fixture(autouse=True)
def clear_global_manifests_cache() -> None:
    # Clear the global cache before each test
    cache_clear = getattr(_manifests, "cache_clear", None)
    if callable(cache_clear):
        # pyiceberg<=0.10: _manifests was lru_cache-decorated.
        cache_clear()
        return

    # pyiceberg>=0.11: manifests are cached in module-level LRUCache.
    from pyiceberg import manifest as manifest_module

    manifest_cache = getattr(manifest_module, "_manifest_cache", None)
    manifest_cache_lock = getattr(manifest_module, "_manifest_cache_lock", None)

    if manifest_cache is None:
        return

    if manifest_cache_lock is None:
        manifest_cache.clear()
        return

    with manifest_cache_lock:
        manifest_cache.clear()


def fetch_manifest_entry(
    manifest_entry_file: str, io: FileIO, discard_deleted: bool = True
) -> List[ManifestEntry]:
    from pyiceberg_core import manifest

    bs = io.new_input(manifest_entry_file).open().read()
    manifest = manifest.read_manifest_entries(bs)

    # TODO: Don't convert the types
    # but this is the easiest for now until we
    # have the write part in there as well
    def _convert_entry(entry: Any) -> ManifestEntry:
        data_file_args = {
            "content": DataFileContent(entry.data_file.content),
            "file_path": entry.data_file.file_path,
            "file_format": FileFormat(entry.data_file.file_format),
            "partition": [
                p.value() if p is not None else None for p in entry.data_file.partition
            ],
            "record_count": entry.data_file.record_count,
            "file_size_in_bytes": entry.data_file.file_size_in_bytes,
            "column_sizes": entry.data_file.column_sizes,
            "value_counts": entry.data_file.value_counts,
            "null_value_counts": entry.data_file.null_value_counts,
            "nan_value_counts": entry.data_file.nan_value_counts,
            "lower_bounds": entry.data_file.lower_bounds,
            "upper_bounds": entry.data_file.upper_bounds,
            "key_metadata": entry.data_file.key_metadata,
            "split_offsets": entry.data_file.split_offsets,
            "equality_ids": entry.data_file.equality_ids,
            "sort_order_id": entry.data_file.sort_order_id,
        }
        data_file = (
            DataFile.from_args(**data_file_args)
            if hasattr(DataFile, "from_args")
            else DataFile(**data_file_args)
        )

        manifest_entry_args = {
            "status": ManifestEntryStatus(entry.status),
            "snapshot_id": entry.snapshot_id,
            "sequence_number": entry.sequence_number,
            "file_sequence_number": entry.file_sequence_number,
            "data_file": data_file,
        }
        return (
            ManifestEntry.from_args(**manifest_entry_args)
            if hasattr(ManifestEntry, "from_args")
            else ManifestEntry(**manifest_entry_args)
        )

    return [
        _convert_entry(entry)
        # _inherit_from_manifest(, self)
        for entry in manifest.entries()
        if not discard_deleted or entry.status != ManifestEntryStatus.DELETED
    ]


def test_read_manifest_entry(generated_manifest_entry_file: str) -> None:
    # manifest = ManifestFile.from_args(
    #     manifest_path=generated_manifest_entry_file,
    #     manifest_length=0,
    #     partition_spec_id=0,
    #     added_snapshot_id=0,
    #     sequence_number=0,
    #     partitions=[],
    # )
    # manifest_entries = manifest.fetch_manifest_entry(PyArrowFileIO())
    manifest_entries = fetch_manifest_entry(
        f"file://{generated_manifest_entry_file}", PyArrowFileIO()
    )
    manifest_entry = manifest_entries[0]

    assert manifest_entry.status == ManifestEntryStatus.ADDED
    assert manifest_entry.snapshot_id == 8744736658442914487
    assert manifest_entry.sequence_number == 0
    assert isinstance(manifest_entry.data_file, DataFile)

    data_file = manifest_entry.data_file

    assert data_file.content == DataFileContent.DATA
    assert (
        data_file.file_path
        == "/home/iceberg/warehouse/nyc/taxis_partitioned/data/VendorID=null/00000-633-d8a4223e-dc97-45a1-86e1-adaba6e8abd7-00001.parquet"
    )
    assert data_file.file_format == FileFormat.PARQUET
    assert repr(data_file.partition) == "[1, None]"
    assert data_file.record_count == 19513
    assert data_file.file_size_in_bytes == 388872
    assert data_file.column_sizes == {
        1: 53,
        2: 98153,
    }
    assert data_file.value_counts == {
        1: 19513,
        2: 19513,
    }
    assert data_file.null_value_counts == {
        1: 19513,
        2: 0,
    }
    assert data_file.nan_value_counts == {}
    assert data_file.lower_bounds == {
        1: b"\x00\x00\x00\x00",
        2: b"\x00\x00\x00\x00\x00\x00\x00\x00",
    }
    assert data_file.upper_bounds == {
        1: b"\x00\x00\xe0\xbf",
        2: b"\x00\x00\x00\x00\x00\x00\xe0\xbf",
    }
    assert data_file.key_metadata is None
    assert data_file.split_offsets == [4]
    assert data_file.equality_ids is None
    assert data_file.sort_order_id == 0
