# SPDX-License-Identifier: PROPRIETARY
# SPDX-FileCopyrightText: Copyright The Geneva Authors

from collections import Counter

from geneva.apply.table_cache import (
    TableCache,
    bind_tables_for_task,
    clear_bound_tables,
)
from geneva.apply.task import CopyTask, ScanTask
from geneva.table import TableReference


def test_table_cache_reuses_table(monkeypatch) -> None:
    calls: list[tuple[tuple[str, ...], int | None]] = []

    def fake_open(self: TableReference) -> object:
        calls.append((tuple(self.table_id), self.version))
        return object()

    monkeypatch.setattr(TableReference, "open", fake_open, raising=True)

    cache = TableCache()
    ref = TableReference(table_id=["db", "tbl"], version=3, db_uri="db://example")

    table_one = cache.get_or_open(ref)
    table_two = cache.get_or_open(ref)

    assert table_one is table_two
    assert calls == [(("db", "tbl"), 3)]


def test_bind_tables_for_scan_task_reuses_open(monkeypatch) -> None:
    calls: Counter[tuple[tuple[str, ...], int | None]] = Counter()

    def fake_open(self: TableReference) -> object:
        key = (tuple(self.table_id), self.version)
        calls[key] += 1
        return object()

    monkeypatch.setattr(TableReference, "open", fake_open, raising=True)

    cache = TableCache()
    ref = TableReference(table_id=["tbl"], version=None, db_uri="db://example")

    task_one = ScanTask(
        uri="db://example/tbl",
        table_ref=ref,
        columns=["a"],
        frag_id=0,
        offset=0,
        limit=10,
        version=5,
    )
    task_two = ScanTask(
        uri="db://example/tbl",
        table_ref=ref,
        columns=["a"],
        frag_id=1,
        offset=0,
        limit=10,
        version=5,
    )

    bind_tables_for_task(task_one, cache)
    bind_tables_for_task(task_two, cache)

    assert sum(calls.values()) == 1
    assert task_one._get_table() is task_two._get_table()
    clear_bound_tables(task_one)
    clear_bound_tables(task_two)
    assert task_one._table is None
    assert task_two._table is None


def test_bind_tables_for_copy_task_reuses_open(monkeypatch) -> None:
    calls: Counter[tuple[tuple[str, ...], int | None]] = Counter()

    def fake_open(self: TableReference) -> object:
        key = (tuple(self.table_id), self.version)
        calls[key] += 1
        return object()

    monkeypatch.setattr(TableReference, "open", fake_open, raising=True)

    cache = TableCache()
    src_ref = TableReference(table_id=["src"], version=1, db_uri="db://example")
    dst_ref = TableReference(table_id=["dst"], version=2, db_uri="db://example")

    task_one = CopyTask(
        src=src_ref,
        dst=dst_ref,
        columns=["a"],
        frag_id=0,
        offset=0,
        limit=5,
    )
    task_two = CopyTask(
        src=src_ref,
        dst=dst_ref,
        columns=["a"],
        frag_id=1,
        offset=5,
        limit=5,
    )

    bind_tables_for_task(task_one, cache)
    bind_tables_for_task(task_two, cache)

    assert calls[(("src",), 1)] == 1
    assert calls[(("dst",), 2)] == 1
    assert task_one._src_table is task_two._src_table
    assert task_one._dst_table is task_two._dst_table
    clear_bound_tables(task_one)
    clear_bound_tables(task_two)
    assert task_one._src_table is None
    assert task_one._dst_table is None
    assert task_two._src_table is None
    assert task_two._dst_table is None


def test_table_cache_distinguishes_missing_and_empty_system_namespace(
    monkeypatch,
) -> None:
    calls: list[tuple[list[str] | None, int | None]] = []

    def fake_open(self: TableReference) -> object:
        calls.append((self.system_namespace, self.version))
        return object()

    monkeypatch.setattr(TableReference, "open", fake_open, raising=True)

    cache = TableCache()
    implicit_ref = TableReference(table_id=["tbl"], version=None, db_uri="db://example")
    explicit_root_ref = TableReference(
        table_id=["tbl"], version=None, db_uri="db://example", system_namespace=[]
    )

    implicit_table = cache.get_or_open(implicit_ref)
    explicit_root_table = cache.get_or_open(explicit_root_ref)

    assert implicit_table is not explicit_root_table
    assert calls == [(None, None), ([], None)]
