"""chunker 单元测试"""

from index1.chunker import get_chunker, Chunk, estimate_tokens
from index1.chunker.markdown import MarkdownChunker
from index1.chunker.python_code import PythonChunker
from index1.chunker.text import TextChunker
from index1.chunker.rust_code import RustChunker, JavaScriptChunker


class TestRegistry:
    def test_markdown(self):
        c = get_chunker("docs/api.md")
        assert isinstance(c, MarkdownChunker)

    def test_python(self):
        c = get_chunker("src/main.py")
        assert isinstance(c, PythonChunker)

    def test_rust(self):
        c = get_chunker("src/lib.rs")
        assert isinstance(c, RustChunker)

    def test_javascript(self):
        for ext in [".js", ".ts", ".jsx", ".tsx"]:
            c = get_chunker(f"app/index{ext}")
            assert isinstance(c, JavaScriptChunker)

    def test_unknown_fallback(self):
        c = get_chunker("data.csv")
        assert isinstance(c, TextChunker)


class TestMarkdownChunker:
    def test_basic_headings(self, sample_markdown):
        chunks = MarkdownChunker().chunk(sample_markdown, "test.md")
        assert len(chunks) >= 2
        sections = [c.section for c in chunks]
        assert any("Liquidation" in (s or "") for s in sections)
        assert any("Margin" in (s or "") for s in sections)

    def test_parent_tracking(self, sample_markdown):
        chunks = MarkdownChunker().chunk(sample_markdown, "test.md")
        # ## Liquidation 的 parent 应该是 # Risk Module
        liq_chunk = [c for c in chunks if c.section and "Liquidation" in c.section]
        assert liq_chunk
        assert liq_chunk[0].parent_section is not None
        assert "Risk" in liq_chunk[0].parent_section

    def test_frontmatter_stripped(self):
        content = """---
title: Test
---

# Hello

World
"""
        chunks = MarkdownChunker().chunk(content, "test.md")
        # frontmatter 不应出现在 chunk 内容中
        all_content = " ".join(c.content for c in chunks)
        assert "title: Test" not in all_content

    def test_code_block_hash_not_heading(self):
        content = """# Real Heading

Some text.

```python
# This is a comment, not a heading
x = 1
```

## Another Heading

More text.
"""
        chunks = MarkdownChunker().chunk(content, "test.md")
        sections = [c.section for c in chunks if c.section]
        # "# This is a comment" 不应被当作标题
        assert not any("comment" in s.lower() for s in sections)

    def test_empty_content(self):
        assert MarkdownChunker().chunk("", "test.md") == []

    def test_no_headings(self):
        chunks = MarkdownChunker().chunk("Just some plain text.", "test.md")
        assert len(chunks) == 1

    def test_tags_extracted(self, sample_markdown):
        chunks = MarkdownChunker().chunk(sample_markdown, "test.md")
        liq = [c for c in chunks if c.section and "Liquidation" in c.section]
        assert liq
        assert "liquidation" in liq[0].tags

    def test_chunk_has_summary(self, sample_markdown):
        chunks = MarkdownChunker().chunk(sample_markdown, "test.md")
        for c in chunks:
            assert c.summary  # 每个 chunk 都应有摘要

    def test_chunk_has_token_count(self, sample_markdown):
        chunks = MarkdownChunker().chunk(sample_markdown, "test.md")
        for c in chunks:
            assert c.token_count > 0


class TestPythonChunker:
    def test_basic_split(self, sample_python):
        chunks = PythonChunker().chunk(sample_python, "test.py")
        sections = [c.section for c in chunks]
        assert "module_header" in sections
        assert "class RiskEngine" in sections
        assert "def helper" in sections

    def test_syntax_error_fallback(self):
        bad_code = "def broken(\n    x = "
        chunks = PythonChunker().chunk(bad_code, "bad.py")
        # 应该降级到 TextChunker，不 crash
        assert len(chunks) >= 1

    def test_class_as_one_chunk(self, sample_python):
        chunks = PythonChunker().chunk(sample_python, "test.py")
        class_chunks = [c for c in chunks if c.section and "RiskEngine" in c.section]
        # 小 class 应该是一个 chunk
        assert len(class_chunks) == 1
        assert "liquidate" in class_chunks[0].content
        assert "margin_call" in class_chunks[0].content

    def test_empty_file(self):
        chunks = PythonChunker().chunk("", "empty.py")
        assert chunks == []


class TestTextChunker:
    def test_single_paragraph(self):
        chunks = TextChunker().chunk("Short text.", "test.txt")
        assert len(chunks) == 1
        assert chunks[0].content == "Short text."

    def test_multiple_paragraphs(self):
        text = "Para one.\n\nPara two.\n\nPara three."
        chunks = TextChunker().chunk(text, "test.txt")
        # 小段落应合并为一个 chunk
        assert len(chunks) == 1

    def test_empty(self):
        assert TextChunker().chunk("", "test.txt") == []
        assert TextChunker().chunk("   \n\n  ", "test.txt") == []


class TestRustChunker:
    def test_basic_rust(self):
        code = """use std::io;

fn main() {
    println!("hello");
}

pub fn helper(x: i32) -> i32 {
    x + 1
}

struct Config {
    name: String,
}
"""
        chunks = RustChunker().chunk(code, "main.rs")
        assert len(chunks) >= 3
        sections = [c.section for c in chunks]
        assert any("fn main" in (s or "") for s in sections)
        assert any("helper" in (s or "") for s in sections)

    def test_single_function_fallback(self):
        code = "fn only_one() { }"
        chunks = RustChunker().chunk(code, "one.rs")
        # 只有 1 个分割点，降级到 TextChunker
        assert len(chunks) >= 1


class TestJavaScriptChunker:
    def test_basic_js(self):
        code = """import React from 'react';

function App() {
  return <div>Hello</div>;
}

class Counter extends React.Component {
  render() {
    return <span>0</span>;
  }
}

export const helper = (x) => x + 1;
"""
        chunks = JavaScriptChunker().chunk(code, "app.jsx")
        assert len(chunks) >= 3
        sections = [c.section or "" for c in chunks]
        assert any("function App" in s for s in sections)
        assert any("class Counter" in s for s in sections)


class TestEstimateTokens:
    def test_english(self):
        assert estimate_tokens("hello world") > 0

    def test_empty(self):
        assert estimate_tokens("") == 1  # min 1

    def test_chinese(self):
        t = estimate_tokens("这是一段中文文字，用来测试 token 估算。")
        assert t > 3
