import os

import pytest

from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language

pytestmark = pytest.mark.vue


class TestVueRename:
    @pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
    def test_rename_function_within_single_file(self, language_server: SolidLanguageServer) -> None:
        file_path = os.path.join("src", "components", "CalculatorInput.vue")

        symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
        handle_digit_symbol = next((s for s in symbols[0] if s.get("name") == "handleDigit"), None)

        if not handle_digit_symbol or "selectionRange" not in handle_digit_symbol:
            pytest.skip("handleDigit symbol not found - test fixture may need updating")

        sel_start = handle_digit_symbol["selectionRange"]["start"]

        workspace_edit = language_server.request_rename_symbol_edit(file_path, sel_start["line"], sel_start["character"], "processDigit")

        assert workspace_edit is not None, "Should return WorkspaceEdit for rename operation"

        has_changes = "changes" in workspace_edit and workspace_edit["changes"]
        has_document_changes = "documentChanges" in workspace_edit and workspace_edit["documentChanges"]

        assert has_changes or has_document_changes, "WorkspaceEdit should contain either 'changes' or 'documentChanges'"

        if has_changes:
            changes = workspace_edit["changes"]
            assert len(changes) > 0, "Should have at least one file with changes"

            calculator_input_files = [uri for uri in changes.keys() if "CalculatorInput.vue" in uri]
            assert len(calculator_input_files) > 0, f"Should have edits for CalculatorInput.vue. Found edits for: {list(changes.keys())}"

            file_edits = changes[calculator_input_files[0]]
            assert len(file_edits) > 0, "Should have at least one TextEdit for the renamed symbol"

            for edit in file_edits:
                assert "range" in edit, "TextEdit should have a range"
                assert "newText" in edit, "TextEdit should have newText"
                assert edit["newText"] == "processDigit", f"newText should be 'processDigit', got {edit['newText']}"

                assert "start" in edit["range"], "Range should have start position"
                assert "end" in edit["range"], "Range should have end position"
                assert "line" in edit["range"]["start"], "Start position should have line number"
                assert "character" in edit["range"]["start"], "Start position should have character offset"

        elif has_document_changes:
            document_changes = workspace_edit["documentChanges"]
            assert isinstance(document_changes, list), "documentChanges should be a list"
            assert len(document_changes) > 0, "Should have at least one document change"

            calculator_input_changes = [dc for dc in document_changes if "CalculatorInput.vue" in dc.get("textDocument", {}).get("uri", "")]
            assert len(calculator_input_changes) > 0, "Should have edits for CalculatorInput.vue"

            for change in calculator_input_changes:
                assert "textDocument" in change, "Document change should have textDocument"
                assert "edits" in change, "Document change should have edits"

                edits = change["edits"]
                assert len(edits) > 0, "Should have at least one TextEdit for the renamed symbol"

                for edit in edits:
                    assert "range" in edit, "TextEdit should have a range"
                    assert "newText" in edit, "TextEdit should have newText"
                    assert edit["newText"] == "processDigit", f"newText should be 'processDigit', got {edit['newText']}"

                    assert "start" in edit["range"], "Range should have start position"
                    assert "end" in edit["range"], "Range should have end position"
                    assert "line" in edit["range"]["start"], "Start position should have line number"
                    assert "character" in edit["range"]["start"], "Start position should have character offset"

    @pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
    def test_rename_composable_function_cross_file(self, language_server: SolidLanguageServer) -> None:
        composable_file = os.path.join("src", "composables", "useFormatter.ts")

        symbols = language_server.request_document_symbols(composable_file).get_all_symbols_and_roots()
        use_formatter_symbol = next((s for s in symbols[0] if s.get("name") == "useFormatter"), None)

        if not use_formatter_symbol or "selectionRange" not in use_formatter_symbol:
            pytest.skip("useFormatter symbol not found - test fixture may need updating")

        sel_start = use_formatter_symbol["selectionRange"]["start"]

        workspace_edit = language_server.request_rename_symbol_edit(
            composable_file, sel_start["line"], sel_start["character"], "useNumberFormatter"
        )

        assert workspace_edit is not None, "Should return WorkspaceEdit for cross-file rename"

        has_changes = "changes" in workspace_edit and workspace_edit["changes"]
        has_document_changes = "documentChanges" in workspace_edit and workspace_edit["documentChanges"]

        assert has_changes or has_document_changes, "WorkspaceEdit should contain either 'changes' or 'documentChanges'"

        if has_changes:
            changes = workspace_edit["changes"]
            assert len(changes) > 0, "Should have at least one file with changes"

            composable_files = [uri for uri in changes.keys() if "useFormatter.ts" in uri]
            assert len(composable_files) > 0, f"Should have edits for useFormatter.ts (definition). Found edits for: {list(changes.keys())}"

            for uri, edits in changes.items():
                assert len(edits) > 0, f"File {uri} should have at least one edit"

                for edit in edits:
                    assert "range" in edit, f"TextEdit in {uri} should have a range"
                    assert "newText" in edit, f"TextEdit in {uri} should have newText"
                    assert edit["newText"] == "useNumberFormatter", f"newText should be 'useNumberFormatter', got {edit['newText']}"
                    assert "start" in edit["range"], f"Range in {uri} should have start position"
                    assert "end" in edit["range"], f"Range in {uri} should have end position"

        elif has_document_changes:
            document_changes = workspace_edit["documentChanges"]
            assert isinstance(document_changes, list), "documentChanges should be a list"
            assert len(document_changes) > 0, "Should have at least one document change"

            composable_changes = [dc for dc in document_changes if "useFormatter.ts" in dc.get("textDocument", {}).get("uri", "")]
            assert len(composable_changes) > 0, (
                f"Should have edits for useFormatter.ts (definition). Found changes for: {[dc.get('textDocument', {}).get('uri', '') for dc in document_changes]}"
            )

            for change in document_changes:
                assert "textDocument" in change, "Document change should have textDocument"
                assert "edits" in change, "Document change should have edits"

                uri = change["textDocument"]["uri"]
                edits = change["edits"]
                assert len(edits) > 0, f"File {uri} should have at least one edit"

                for edit in edits:
                    assert "range" in edit, f"TextEdit in {uri} should have a range"
                    assert "newText" in edit, f"TextEdit in {uri} should have newText"
                    assert edit["newText"] == "useNumberFormatter", f"newText should be 'useNumberFormatter', got {edit['newText']}"
                    assert "start" in edit["range"], f"Range in {uri} should have start position"
                    assert "end" in edit["range"], f"Range in {uri} should have end position"

    @pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
    def test_rename_verifies_correct_file_paths_and_ranges(self, language_server: SolidLanguageServer) -> None:
        file_path = os.path.join("src", "App.vue")

        symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
        app_title_symbol = next((s for s in symbols[0] if s.get("name") == "appTitle"), None)

        if not app_title_symbol or "selectionRange" not in app_title_symbol:
            pytest.skip("appTitle symbol not found - test fixture may need updating")

        sel_start = app_title_symbol["selectionRange"]["start"]

        workspace_edit = language_server.request_rename_symbol_edit(
            file_path, sel_start["line"], sel_start["character"], "applicationTitle"
        )

        assert workspace_edit is not None, "Should return WorkspaceEdit for rename operation"
        assert isinstance(workspace_edit, dict), "WorkspaceEdit should be a dictionary"

        has_changes = "changes" in workspace_edit and workspace_edit["changes"]
        has_document_changes = "documentChanges" in workspace_edit and workspace_edit["documentChanges"]

        assert has_changes or has_document_changes, "WorkspaceEdit must have 'changes' or 'documentChanges'"

        if has_changes:
            changes = workspace_edit["changes"]

            assert isinstance(changes, dict), "changes should be a dict mapping URIs to TextEdit lists"

            assert len(changes) > 0, "Should have edits for at least one file"

            for uri, edits in changes.items():
                assert isinstance(uri, str), f"URI should be a string, got {type(uri)}"
                assert uri.startswith("file://"), f"URI should start with 'file://', got {uri}"

                assert isinstance(edits, list), f"Edits for {uri} should be a list, got {type(edits)}"
                assert len(edits) > 0, f"Should have at least one edit for {uri}"

                for idx, edit in enumerate(edits):
                    assert isinstance(edit, dict), f"Edit {idx} in {uri} should be a dict, got {type(edit)}"

                    assert "range" in edit, f"Edit {idx} in {uri} missing 'range'"
                    assert "newText" in edit, f"Edit {idx} in {uri} missing 'newText'"

                    range_obj = edit["range"]
                    assert "start" in range_obj, f"Edit {idx} range in {uri} missing 'start'"
                    assert "end" in range_obj, f"Edit {idx} range in {uri} missing 'end'"

                    for pos_name in ["start", "end"]:
                        pos = range_obj[pos_name]
                        assert "line" in pos, f"Edit {idx} range {pos_name} in {uri} missing 'line'"
                        assert "character" in pos, f"Edit {idx} range {pos_name} in {uri} missing 'character'"
                        assert isinstance(pos["line"], int), f"Line should be int, got {type(pos['line'])}"
                        assert isinstance(pos["character"], int), f"Character should be int, got {type(pos['character'])}"
                        assert pos["line"] >= 0, f"Line number should be >= 0, got {pos['line']}"
                        assert pos["character"] >= 0, f"Character offset should be >= 0, got {pos['character']}"

                    assert isinstance(edit["newText"], str), f"newText should be string, got {type(edit['newText'])}"
                    assert edit["newText"] == "applicationTitle", f"newText should be 'applicationTitle', got {edit['newText']}"

        elif has_document_changes:
            document_changes = workspace_edit["documentChanges"]
            assert isinstance(document_changes, list), "documentChanges should be a list"
            assert len(document_changes) > 0, "Should have at least one document change"

            for change in document_changes:
                assert isinstance(change, dict), "Each document change should be a dict"
                assert "textDocument" in change, "Document change should have textDocument"
                assert "edits" in change, "Document change should have edits"

                text_doc = change["textDocument"]
                assert "uri" in text_doc, "textDocument should have uri"
                assert text_doc["uri"].startswith("file://"), f"URI should start with 'file://', got {text_doc['uri']}"

                edits = change["edits"]
                assert isinstance(edits, list), "edits should be a list"
                assert len(edits) > 0, "Should have at least one edit"

                for idx, edit in enumerate(edits):
                    assert isinstance(edit, dict), f"Edit {idx} in {text_doc['uri']} should be a dict, got {type(edit)}"

                    assert "range" in edit, f"Edit {idx} in {text_doc['uri']} missing 'range'"
                    assert "newText" in edit, f"Edit {idx} in {text_doc['uri']} missing 'newText'"

                    range_obj = edit["range"]
                    assert "start" in range_obj, f"Edit {idx} range in {text_doc['uri']} missing 'start'"
                    assert "end" in range_obj, f"Edit {idx} range in {text_doc['uri']} missing 'end'"

                    for pos_name in ["start", "end"]:
                        pos = range_obj[pos_name]
                        assert "line" in pos, f"Edit {idx} range {pos_name} in {text_doc['uri']} missing 'line'"
                        assert "character" in pos, f"Edit {idx} range {pos_name} in {text_doc['uri']} missing 'character'"
                        assert isinstance(pos["line"], int), f"Line should be int, got {type(pos['line'])}"
                        assert isinstance(pos["character"], int), f"Character should be int, got {type(pos['character'])}"
                        assert pos["line"] >= 0, f"Line number should be >= 0, got {pos['line']}"
                        assert pos["character"] >= 0, f"Character offset should be >= 0, got {pos['character']}"

                    assert isinstance(edit["newText"], str), f"newText should be string, got {type(edit['newText'])}"
                    assert edit["newText"] == "applicationTitle", f"newText should be 'applicationTitle', got {edit['newText']}"
