from __future__ import annotations

import os
import shutil
import sys
import tempfile
import json
from pathlib import Path

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from vibelexity.circular_deps import find_cycles
from vibelexity.circular_deps.parsers.typescript import TypeScriptImportParser


def test_typescript_parser_extract_import():
    parser = TypeScriptImportParser()
    source = """
import { foo } from './b';
import bar from './c';
export * from './d';
"""
    imports = parser.extract_imports(source, "test.ts")
    assert len(imports) == 3
    assert imports[0].raw_module == "./b"
    assert imports[1].raw_module == "./c"
    assert imports[2].raw_module == "./d"
    assert all(imp.line > 0 for imp in imports)


def test_typescript_cycle_detection_simple():
    tmpdir = Path(tempfile.mkdtemp())
    try:
        a_ts = tmpdir / "a.ts"
        b_ts = tmpdir / "b.ts"

        a_ts.write_text(
            "import { foo } from './b.ts';\nexport const bar = () => 'bar';\n"
        )
        b_ts.write_text(
            "import { bar } from './a.ts';\nexport const foo = () => 'foo';\n"
        )

        files = [a_ts, b_ts]
        cycles = find_cycles(files)

        assert len(cycles) == 1
        assert cycles[0].depth == 2
        assert cycles[0].severity == "warning"
    finally:
        shutil.rmtree(tmpdir)


def test_typescript_resolver_relative_imports():
    from vibelexity.circular_deps.resolvers.typescript import TypeScriptPathResolver

    tmpdir = Path(tempfile.mkdtemp())
    try:
        a_ts = tmpdir / "a.ts"
        b_dir = tmpdir / "b"
        b_sub_dir = b_dir / "c"
        b_ts = b_dir / "b.ts"
        sub_ts = b_sub_dir / "sub.ts"

        a_ts.write_text("")
        b_dir.mkdir(parents=True, exist_ok=True)
        b_sub_dir.mkdir(parents=True, exist_ok=True)
        b_ts.write_text("")
        sub_ts.write_text("")

        resolver = TypeScriptPathResolver(module_resolution="node")

        path = resolver.resolve("./b", str(a_ts), str(tmpdir))
        assert path is not None
        assert path == str(b_ts)

        path = resolver.resolve("./c/sub.ts", str(b_ts), str(tmpdir))
        assert path is not None
        assert path == str(sub_ts)
    finally:
        shutil.rmtree(tmpdir)


def test_typescript_resolver_index_precedence():
    from vibelexity.circular_deps.resolvers.typescript import TypeScriptPathResolver

    tmpdir = Path(tempfile.mkdtemp())
    try:
        dir_path = tmpdir / "pkg"
        index_ts = dir_path / "index.ts"
        index_tsx = dir_path / "index.tsx"
        a_ts = tmpdir / "a.ts"

        dir_path.mkdir(parents=True, exist_ok=True)
        a_ts.write_text("")

        index_ts.write_text("")
        resolver = TypeScriptPathResolver()
        path = resolver.resolve("./pkg/index.ts", str(a_ts), str(tmpdir))
        assert path == str(index_ts)

        index_tsx.write_text("")
        path = resolver.resolve("./pkg/index.ts", str(a_ts), str(tmpdir))
        assert path == str(index_ts)
    finally:
        shutil.rmtree(tmpdir)


def test_typescript_no_external_imports():
    from vibelexity.circular_deps.resolvers.typescript import TypeScriptPathResolver

    resolver = TypeScriptPathResolver()
    path = resolver.resolve("lodash", "test.ts", "/project")
    path2 = resolver.resolve("react", "test.ts", "/project")
    assert path is None
    assert path2 is None


def test_typescript_cycle_detection_in_src_subdirectory():
    """Test circular dependency detection with files in ./src subdirectory."""
    import subprocess

    tmpdir = Path(tempfile.mkdtemp())
    try:
        src_dir = tmpdir / "src"
        src_dir.mkdir(parents=True, exist_ok=True)

        afet_dir = src_dir / "afet"
        bfet_dir = src_dir / "bfet"
        afet_dir.mkdir(parents=True, exist_ok=True)
        bfet_dir.mkdir(parents=True, exist_ok=True)

        a_ts = afet_dir / "a.ts"
        b_ts = bfet_dir / "b.ts"

        a_ts.write_text(
            "import { foo } from '../bfet/b.js';\nexport const bar = () => 'bar';\n"
        )
        b_ts.write_text(
            "import { bar } from '../afet/a.js';\nexport const foo = () => 'foo';\n"
        )
        files = [a_ts, b_ts]
        cycles = find_cycles(files)

        assert len(cycles) == 1
    finally:
        shutil.rmtree(tmpdir)


def test_typescript_resolver_auto_discovery_from_tsconfig():
    """Auto-discover module resolution from tsconfig.json."""
    from vibelexity.circular_deps.resolvers.typescript import TypeScriptPathResolver

    tmpdir = Path(tempfile.mkdtemp())
    try:
        tsconfig = tmpdir / "tsconfig.json"
        a_ts = tmpdir / "a.ts"

        tsconfig.write_text(
            json.dumps({"compilerOptions": {"moduleResolution": "bundler"}})
        )

        a_ts.write_text("")
        resolver = TypeScriptPathResolver(filepath=str(a_ts))

        assert resolver.module_resolution == "bundler"
    finally:
        shutil.rmtree(tmpdir)


def test_typescript_resolver_auto_discovery_no_tsconfig():
    """Falls back to node16 when no tsconfig.json found."""
    from vibelexity.circular_deps.resolvers.typescript import TypeScriptPathResolver

    tmpdir = Path(tempfile.mkdtemp())
    try:
        a_ts = tmpdir / "a.ts"

        a_ts.write_text("")
        resolver = TypeScriptPathResolver(filepath=str(a_ts))

        assert resolver.module_resolution == "node16"
    finally:
        shutil.rmtree(tmpdir)


def test_typescript_resolver_cli_overrides_tsconfig():
    """CLI argument overrides tsconfig.json."""
    from vibelexity.circular_deps.resolvers.typescript import TypeScriptPathResolver

    tmpdir = Path(tempfile.mkdtemp())
    try:
        tsconfig = tmpdir / "tsconfig.json"
        a_ts = tmpdir / "a.ts"

        tsconfig.write_text(
            json.dumps({"compilerOptions": {"moduleResolution": "node"}})
        )

        a_ts.write_text("")
        resolver = TypeScriptPathResolver(
            module_resolution="bundler", filepath=str(a_ts)
        )

        assert resolver.module_resolution == "bundler"
    finally:
        shutil.rmtree(tmpdir)


def test_typescript_resolver_invalid_tsconfig():
    """Silent fallback for invalid tsconfig.json."""
    from vibelexity.circular_deps.resolvers.typescript import TypeScriptPathResolver

    tmpdir = Path(tempfile.mkdtemp())
    try:
        tsconfig = tmpdir / "tsconfig.json"
        a_ts = tmpdir / "a.ts"

        tsconfig.write_text("{ invalid json")

        a_ts.write_text("")
        resolver = TypeScriptPathResolver(filepath=str(a_ts))

        assert resolver.module_resolution == "node16"
    finally:
        shutil.rmtree(tmpdir)


def test_typescript_resolver_node_mode_with_extension():
    """node mode: literal match only for extensions."""
    from vibelexity.circular_deps.resolvers.typescript import TypeScriptPathResolver

    tmpdir = Path(tempfile.mkdtemp())
    try:
        moa_ts = tmpdir / "moa.ts"
        moa_js = tmpdir / "moa.js"
        a_ts = tmpdir / "a.ts"

        moa_ts.write_text("")
        moa_js.write_text("")
        a_ts.write_text("")

        resolver = TypeScriptPathResolver(module_resolution="node")

        path = resolver.resolve("./moa.js", str(a_ts), str(tmpdir))
        assert path == str(moa_js)

        path = resolver.resolve("./moa.ts", str(a_ts), str(tmpdir))
        assert path == str(moa_ts)
    finally:
        shutil.rmtree(tmpdir)


def test_typescript_resolver_node_mode_without_extension():
    """node mode: .ts -> .tsx -> .d.ts -> .js -> index.*"""
    from vibelexity.circular_deps.resolvers.typescript import TypeScriptPathResolver

    tmpdir = Path(tempfile.mkdtemp())
    try:
        moa_ts = tmpdir / "moa.ts"
        dir_path = tmpdir / "pkg"
        index_ts = dir_path / "index.ts"
        a_ts = tmpdir / "a.ts"

        moa_ts.write_text("")
        a_ts.write_text("")
        dir_path.mkdir(parents=True, exist_ok=True)

        resolver = TypeScriptPathResolver(module_resolution="node")

        path = resolver.resolve("./moa", str(a_ts), str(tmpdir))
        assert path == str(moa_ts)

        index_ts.write_text("")
        path = resolver.resolve("./pkg", str(a_ts), str(tmpdir))
        assert path == str(index_ts)
    finally:
        shutil.rmtree(tmpdir)


def test_typescript_resolver_node16_mode_with_extension():
    """node16 mode: .ext -> .ts -> .tsx"""
    from vibelexity.circular_deps.resolvers.typescript import TypeScriptPathResolver

    tmpdir = Path(tempfile.mkdtemp())
    try:
        moa_ts = tmpdir / "moa.ts"
        moa_jss = tmpdir / "moa.js"
        a_ts = tmpdir / "a.ts"

        moa_jss.write_text("")
        a_ts.write_text("")

        resolver = TypeScriptPathResolver(module_resolution="node16")

        path = resolver.resolve("./moa.js", str(a_ts), str(tmpdir))
        assert path == str(moa_jss)

        moa_ts.write_text("")
        path = resolver.resolve("./moa.ts", str(a_ts), str(tmpdir))
        assert path == str(moa_ts)
    finally:
        shutil.rmtree(tmpdir)


def test_typescript_resolver_node16_mode_without_extension():
    """node16 mode: return None (ESM requires extension)"""
    from vibelexity.circular_deps.resolvers.typescript import TypeScriptPathResolver

    tmpdir = Path(tempfile.mkdtemp())
    try:
        moa_ts = tmpdir / "moa.ts"
        a_ts = tmpdir / "a.ts"

        moa_ts.write_text("")
        a_ts.write_text("")

        resolver = TypeScriptPathResolver(module_resolution="node16")

        path = resolver.resolve("./moa", str(a_ts), str(tmpdir))
        assert path is None
    finally:
        shutil.rmtree(tmpdir)


def test_typescript_resolver_bundler_mode():
    """bundler mode: lenient extension resolution"""
    from vibelexity.circular_deps.resolvers.typescript import TypeScriptPathResolver

    tmpdir = Path(tempfile.mkdtemp())
    try:
        moa_ts = tmpdir / "moa.ts"
        moa_js = tmpdir / "moa.js"
        dir_path = tmpdir / "pkg"
        index_ts = dir_path / "index.ts"
        a_ts = tmpdir / "a.ts"

        moa_ts.write_text("")
        moa_js.write_text("")
        a_ts.write_text("")
        dir_path.mkdir(parents=True, exist_ok=True)

        resolver = TypeScriptPathResolver(module_resolution="bundler")

        path = resolver.resolve("./moa.js", str(a_ts), str(tmpdir))
        assert path == str(moa_js), f"Expected {moa_js}, got {path}"

        path = resolver.resolve("./moa.ts", str(a_ts), str(tmpdir))
        assert path == str(moa_ts), f"Expected {moa_ts}, got {path}"

        index_ts.write_text("")
        path = resolver.resolve("./pkg", str(a_ts), str(tmpdir))
        assert path is not None
    finally:
        shutil.rmtree(tmpdir)


def test_typescript_resolver_closest_tsconfig():
    """Use tsconfig closest to file being analyzed."""
    from vibelexity.circular_deps.resolvers.typescript import TypeScriptPathResolver

    tmpdir = Path(tempfile.mkdtemp())
    try:
        tsconfig_root = tmpdir / "tsconfig.json"
        tsconfig_inner = tmpdir / "inner" / "tsconfig.json"
        outer_ts = tmpdir / "outer.ts"
        inner_ts = tmpdir / "inner" / "inner.ts"

        tsconfig_root.write_text(
            json.dumps({"compilerOptions": {"moduleResolution": "node16"}})
        )

        (tmpdir / "inner").mkdir(parents=True, exist_ok=True)

        tsconfig_inner.write_text(
            json.dumps({"compilerOptions": {"moduleResolution": "bundler"}})
        )

        outer_ts.write_text("")
        inner_ts.write_text("")

        resolver = TypeScriptPathResolver(filepath=str(outer_ts))
        assert resolver.module_resolution == "node16"

        resolver = TypeScriptPathResolver(filepath=str(inner_ts))
        assert resolver.module_resolution == "bundler"
    finally:
        shutil.rmtree(tmpdir)


def test_typescript_cycle_nested_to_root():
    """Test circular dependency with src/analysis/verticality.ts → src/index.ts → src/analysis/verticality.ts"""
    tmpdir = Path(tempfile.mkdtemp())
    try:
        src_dir = tmpdir / "src"
        src_dir.mkdir(parents=True, exist_ok=True)

        analysis_dir = src_dir / "analysis"
        analysis_dir.mkdir(parents=True, exist_ok=True)

        verticality_ts = analysis_dir / "verticality.js"
        index_ts = src_dir / "index.js"

        verticality_ts.write_text(
            "import { foo } from '../index.js';\nexport const bar = () => 'bar';\n"
        )
        index_ts.write_text(
            "import { bar } from './analysis/verticality.js';\nexport const foo = () => 'foo';\n"
        )

        import subprocess

        cmd = ["uv", "run", "vibelexity", "check-cycles", str(tmpdir)]
        output = subprocess.run(cmd, text=True, capture_output=True)
        print(output)
        # files = [verticality_ts, index_ts]
        # cycles = find_cycles(files)

        # assert len(cycles) == 1
        # assert cycles[0].depth == 2
    finally:
        shutil.rmtree(tmpdir)
