import gzip
import json
from pathlib import Path
from types import SimpleNamespace

import nemo_library_etl.adapter.migman.extract_from_database as mod
from nemo_library_etl.adapter.migman.extract_from_database import (
    MigManExtractFromDatabase,
)


class DummyLogger:
    def info(self, *args, **kwargs):
        pass

    def debug(self, *args, **kwargs):
        pass

    def warning(self, *args, **kwargs):
        pass

    def error(self, *args, **kwargs):
        pass


class DummyLocalDatabase:
    def extract_tables(self, sql):
        return ["kunden", "adressen"]

    def extract_fields_by_base_table(self, sql):
        return {
            "kunden": ["kundennummer", "name1"],
            "adressen": ["kundennummer", "strasse"],
        }


def make_cfg(
    *,
    etl_directory="tests/.tmp_migman_etl",
    table_prefixes=None,
    table_selector="all",
    tables=None,
):
    if table_prefixes is None:
        table_prefixes = ["dbo", "  arc  ", "dbo.", "", None]
    if tables is None:
        tables = ["kunden", "adressen"]

    inforas = SimpleNamespace(
        table_prefixes=table_prefixes,
        table_selector=table_selector,
        tables=tables,
    )

    extract = SimpleNamespace(
        inforas=inforas,
    )

    return SimpleNamespace(
        etl_directory=etl_directory,
        extract=extract,
    )


def build_extractor(cfg=None, local_database=None):
    if cfg is None:
        cfg = make_cfg()
    if local_database is None:
        local_database = DummyLocalDatabase()

    return MigManExtractFromDatabase(
        nl=SimpleNamespace(),
        cfg=cfg,
        logger=DummyLogger(),
        fh=SimpleNamespace(),
        local_database=local_database,
    )


def test_normalize_inforas_table_prefixes_strips_adds_dot_and_deduplicates():
    extractor = build_extractor(
        make_cfg(table_prefixes=["dbo", " arc ", "dbo.", "", None, "arc"])
    )

    result = extractor._normalize_inforas_table_prefixes()

    assert result == ["dbo.", "arc."]


def test_resolve_inforas_source_table_returns_first_matching_prefix(monkeypatch):
    extractor = build_extractor()

    monkeypatch.setattr(
        extractor,
        "_normalize_inforas_table_prefixes",
        lambda: ["dbo.", "hist.", "live."],
    )
    monkeypatch.setattr(
        extractor,
        "_inforas_table_exists",
        lambda conn, source_table_name: source_table_name == "hist.kunden",
    )

    resolved = extractor._resolve_inforas_source_table(
        conn=object(),
        table_name="kunden",
    )

    assert resolved == "hist.kunden"


def test_get_tables_and_fields_all_selector_returns_all_tables_with_star():
    cfg = make_cfg(
        table_selector="all",
        tables=["kunden", "adressen"],
    )
    extractor = build_extractor(cfg=cfg)

    tables, fields = extractor.get_tables_and_fields("inforas")

    assert set(tables) == {"kunden", "adressen"}
    assert fields == {
        "kunden": ["*"],
        "adressen": ["*"],
    }


def test_get_tables_and_fields_join_parser_uses_join_query(monkeypatch):
    cfg = make_cfg(table_selector="join_parser")
    extractor = build_extractor()

    monkeypatch.setattr(
        mod.MigManUtils,
        "get_migman_projects",
        staticmethod(lambda cfg: ["Customers", "Suppliers"]),
    )
    monkeypatch.setattr(
        mod.MigManUtils,
        "getJoinQuery",
        staticmethod(lambda adapter, project: f"SELECT * FROM {project}"),
    )

    tables, fields = extractor.get_tables_and_fields("inforas")

    assert set(tables) == {"kunden", "adressen"}
    assert "kunden" in fields
    assert "adressen" in fields
    assert "kundennummer" in fields["kunden"]


def test_sanitize_json_keys_replaces_backslashes_and_slashes_recursively():
    extractor = build_extractor()

    obj = {
        "a/b": {
            "c\\d": 1,
            "list": [
                {"x/y": 2},
                {"m\\n": 3},
            ],
        }
    }

    result = extractor._sanitize_json_keys(obj)

    assert result == {
        "a_b": {
            "c_d": 1,
            "list": [
                {"x_y": 2},
                {"m_n": 3},
            ],
        }
    }


def test_sanitize_jsonl_files_for_entity_rewrites_plain_jsonl_to_lowercase(tmp_path):
    etl_dir = tmp_path / "etl"
    extract_dir = etl_dir / "extract"
    extract_dir.mkdir(parents=True)

    source_file = extract_dir / "KUNDEN.jsonl"
    source_file.write_text(
        json.dumps({"a/b": 1, "c\\d": 2}, ensure_ascii=False) + "\n"
        + json.dumps(["not-a-dict"], ensure_ascii=False) + "\n",
        encoding="utf-8",
    )

    extractor = build_extractor(make_cfg(etl_directory=str(etl_dir)))
    extractor._sanitize_jsonl_files_for_entity("kunden")

    lower_file = extract_dir / "kunden.jsonl"

    assert lower_file.exists()
    assert not source_file.exists()

    lines = lower_file.read_text(encoding="utf-8").strip().splitlines()
    assert len(lines) == 1
    assert json.loads(lines[0]) == {"a_b": 1, "c_d": 2}


def test_sanitize_jsonl_files_for_entity_rewrites_gz_jsonl_to_lowercase(tmp_path):
    etl_dir = tmp_path / "etl"
    extract_dir = etl_dir / "migman" / "extract"
    extract_dir.mkdir(parents=True)

    source_file = extract_dir / "KUNDEN.jsonl.gz"
    with gzip.open(source_file, "wt", encoding="utf-8") as f:
        f.write(json.dumps({"a/b": 1, "c\\d": 2}, ensure_ascii=False) + "\n")
        f.write(json.dumps("skip-me", ensure_ascii=False) + "\n")

    extractor = build_extractor(make_cfg(etl_directory=str(etl_dir)))
    extractor._sanitize_jsonl_files_for_entity("kunden")

    lower_file = extract_dir / "kunden.jsonl.gz"

    assert lower_file.exists()
    assert not source_file.exists()

    with gzip.open(lower_file, "rt", encoding="utf-8") as f:
        lines = [line.strip() for line in f if line.strip()]

    assert len(lines) == 1
    assert json.loads(lines[0]) == {"a_b": 1, "c_d": 2}