"""Tests for FakeHDFS and DataFrameReader/Writer."""
import os
import tempfile
import pytest
from pathlib import Path

from singlespark import SparkSession
from singlespark.hdfs import FakeHDFS


@pytest.fixture
def tmp_hdfs_root(tmp_path):
    return str(tmp_path / "hdfs_root")


@pytest.fixture
def hdfs(tmp_hdfs_root):
    return FakeHDFS(root=tmp_hdfs_root)


@pytest.fixture
def spark(tmp_hdfs_root):
    s = SparkSession.builder.appName("test_hdfs").config(
        "spark.singlespark.hdfs.root", tmp_hdfs_root
    ).create()
    yield s
    s.stop()


# ---------------------------------------------------------------------------
# FakeHDFS path resolution
# ---------------------------------------------------------------------------

def test_resolve_hdfs_with_authority(hdfs):
    resolved = hdfs.resolve("hdfs://namenode/user/data/file.parquet")
    assert resolved == hdfs.root / "user/data/file.parquet"


def test_resolve_hdfs_triple_slash(hdfs):
    resolved = hdfs.resolve("hdfs:///user/data/file.parquet")
    assert resolved == hdfs.root / "user/data/file.parquet"


def test_resolve_local_absolute(hdfs, tmp_path):
    p = str(tmp_path / "file.txt")
    resolved = hdfs.resolve(p)
    assert resolved == Path(p)


def test_resolve_local_relative(hdfs):
    # Relative paths pass through as Path objects
    resolved = hdfs.resolve("relative/path.txt")
    assert isinstance(resolved, Path)
    assert "relative" in str(resolved)


# ---------------------------------------------------------------------------
# FakeHDFS filesystem ops
# ---------------------------------------------------------------------------

def test_mkdir_and_exists(hdfs):
    hdfs.mkdir("hdfs:///test/dir")
    assert hdfs.exists("hdfs:///test/dir")


def test_ls(hdfs):
    hdfs.mkdir("hdfs:///ls_test/a")
    hdfs.mkdir("hdfs:///ls_test/b")
    contents = hdfs.ls("hdfs:///ls_test")
    assert len(contents) == 2


def test_rm_file(hdfs):
    path = hdfs.resolve("hdfs:///rmtest/file.txt")
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text("hello")
    assert hdfs.exists("hdfs:///rmtest/file.txt")
    hdfs.rm("hdfs:///rmtest/file.txt")
    assert not hdfs.exists("hdfs:///rmtest/file.txt")


def test_rm_dir_recursive(hdfs):
    hdfs.mkdir("hdfs:///rmdir/sub")
    hdfs.rm("hdfs:///rmdir", recursive=True)
    assert not hdfs.exists("hdfs:///rmdir")


def test_rm_dir_without_recursive_raises(hdfs):
    hdfs.mkdir("hdfs:///rmdir2")
    with pytest.raises(IsADirectoryError):
        hdfs.rm("hdfs:///rmdir2", recursive=False)


def test_glob(hdfs, tmp_path):
    base = hdfs.resolve("hdfs:///glob_test")
    base.mkdir(parents=True, exist_ok=True)
    (base / "a.parquet").write_text("x")
    (base / "b.parquet").write_text("x")
    (base / "c.csv").write_text("x")
    results = hdfs.glob("hdfs:///glob_test/*.parquet")
    assert len(results) == 2


def test_copy_from_local(hdfs, tmp_path):
    src = tmp_path / "src.txt"
    src.write_text("hello world")
    hdfs.copyFromLocal(str(src), "hdfs:///copies/src.txt")
    assert hdfs.exists("hdfs:///copies/src.txt")


def test_copy_to_local(hdfs, tmp_path):
    # Put a file in fake HDFS first
    hdfs_path = hdfs.resolve("hdfs:///export/data.txt")
    hdfs_path.parent.mkdir(parents=True, exist_ok=True)
    hdfs_path.write_text("exported content")
    dst = tmp_path / "exported.txt"
    hdfs.copyToLocal("hdfs:///export/data.txt", str(dst))
    assert dst.exists()
    assert dst.read_text() == "exported content"


# ---------------------------------------------------------------------------
# Parquet read/write
# ---------------------------------------------------------------------------

def test_write_read_parquet_hdfs(spark):
    data = [("Alice", 30), ("Bob", 25)]
    df = spark.createDataFrame(data, ["name", "age"])
    df.write.mode("overwrite").parquet("hdfs:///test/parquet_data")
    df2 = spark.read.parquet("hdfs:///test/parquet_data/*.snappy.parquet")
    assert df2.count() == 2
    names = sorted(r["name"] for r in df2.collect())
    assert names == ["Alice", "Bob"]


def test_write_overwrite_mode(spark):
    data = [("Alice", 30)]
    df = spark.createDataFrame(data, ["name", "age"])
    df.write.mode("overwrite").parquet("hdfs:///test/overwrite_test")
    df.write.mode("overwrite").parquet("hdfs:///test/overwrite_test")  # should not raise


def test_write_error_mode_raises(spark):
    data = [("Alice", 30)]
    df = spark.createDataFrame(data, ["name", "age"])
    df.write.mode("overwrite").parquet("hdfs:///test/error_mode_test")
    with pytest.raises(FileExistsError):
        df.write.mode("error").parquet("hdfs:///test/error_mode_test")


def test_write_ignore_mode(spark):
    data = [("Alice", 30)]
    df = spark.createDataFrame(data, ["name", "age"])
    df.write.mode("overwrite").parquet("hdfs:///test/ignore_test")
    df.write.mode("ignore").parquet("hdfs:///test/ignore_test")  # should silently skip


# ---------------------------------------------------------------------------
# CSV read/write
# ---------------------------------------------------------------------------

def test_write_read_csv(spark):
    data = [("Alice", 30), ("Bob", 25)]
    df = spark.createDataFrame(data, ["name", "age"])
    df.write.mode("overwrite").csv("hdfs:///test/csv_data")
    df2 = spark.read.csv("hdfs:///test/csv_data/*.csv", header=True, inferSchema=True)
    assert df2.count() == 2


# ---------------------------------------------------------------------------
# Local path (no hdfs://)
# ---------------------------------------------------------------------------

def test_write_read_parquet_local(spark, tmp_path):
    out_path = str(tmp_path / "local_parquet")
    data = [("x", 1), ("y", 2)]
    df = spark.createDataFrame(data, ["k", "v"])
    df.write.mode("overwrite").parquet(out_path)
    df2 = spark.read.parquet(out_path + "/*.snappy.parquet")
    assert df2.count() == 2
