"""Markdownlint-cli2 Markdown linter integration."""

import json
import os
import subprocess  # nosec B404 - used safely with shell disabled
import tempfile
from dataclasses import dataclass, field

from loguru import logger

from lintro.enums.tool_type import ToolType
from lintro.models.core.tool_config import ToolConfig
from lintro.models.core.tool_result import ToolResult
from lintro.parsers.markdownlint.markdownlint_parser import parse_markdownlint_output
from lintro.tools.core.tool_base import BaseTool
from lintro.utils.config import get_central_line_length
from lintro.utils.tool_utils import walk_files_with_excludes
from lintro.utils.unified_config import DEFAULT_TOOL_PRIORITIES

# Constants
MARKDOWNLINT_DEFAULT_TIMEOUT: int = 30
# Use centralized priority from unified_config.py for consistency
MARKDOWNLINT_DEFAULT_PRIORITY: int = DEFAULT_TOOL_PRIORITIES.get("markdownlint", 30)
MARKDOWNLINT_FILE_PATTERNS: list[str] = [
    "*.md",
    "*.markdown",
]


@dataclass
class MarkdownlintTool(BaseTool):
    """Markdownlint-cli2 Markdown linter integration.

    Markdownlint-cli2 is a linter for Markdown files that checks for style
    issues and best practices.

    Attributes:
        name: Tool name
        description: Tool description
        can_fix: Whether the tool can fix issues
        config: Tool configuration
        exclude_patterns: List of patterns to exclude
        include_venv: Whether to include virtual environment files
    """

    name: str = "markdownlint"
    description: str = "Markdown linter for style checking and best practices"
    can_fix: bool = False
    config: ToolConfig = field(
        default_factory=lambda: ToolConfig(
            priority=MARKDOWNLINT_DEFAULT_PRIORITY,
            conflicts_with=[],
            file_patterns=MARKDOWNLINT_FILE_PATTERNS,
            tool_type=ToolType.LINTER,
            options={
                "timeout": MARKDOWNLINT_DEFAULT_TIMEOUT,
            },
        ),
    )

    def __post_init__(self) -> None:
        """Initialize the tool."""
        super().__post_init__()

    def _verify_tool_version(self) -> ToolResult | None:
        """Verify that markdownlint-cli2 meets minimum version requirements.

        Overrides base implementation to use the correct executable name.

        Returns:
            Optional[ToolResult]: None if version check passes, or a skip result
                if it fails
        """
        from lintro.tools.core.version_requirements import check_tool_version

        # Use the correct command for markdownlint-cli2
        command = self._get_markdownlint_command()
        version_info = check_tool_version(self.name, command)

        if version_info.version_check_passed:
            return None  # Version check passed

        # Version check failed - return skip result with warning
        skip_message = (
            f"Skipping {self.name}: {version_info.error_message}. "
            f"Minimum required: {version_info.min_version}. "
            f"{version_info.install_hint}"
        )

        return ToolResult(
            name=self.name,
            success=True,  # Not an error, just skipping
            output=skip_message,
            issues_count=0,
        )

    def set_options(
        self,
        timeout: int | None = None,
        line_length: int | None = None,
        **kwargs,
    ) -> None:
        """Set Markdownlint-specific options.

        Args:
            timeout: Timeout in seconds per file (default: 30)
            line_length: Line length for MD013 rule. If not provided, uses
                central line_length from [tool.lintro] or falls back to Ruff's
                line-length setting.
            **kwargs: Other tool options

        Raises:
            ValueError: If timeout is not an integer or is not positive, or
                if line_length is not an integer or is not positive
        """
        set_kwargs = dict(kwargs)

        if timeout is not None:
            if not isinstance(timeout, int):
                raise ValueError("timeout must be an integer")
            if timeout <= 0:
                raise ValueError("timeout must be positive")
            set_kwargs["timeout"] = timeout

        # Use provided line_length, or get from central config
        if line_length is None:
            line_length = get_central_line_length()

        if line_length is not None:
            if not isinstance(line_length, int):
                raise ValueError("line_length must be an integer")
            if line_length <= 0:
                raise ValueError("line_length must be positive")
            # Store for use in check() method
            self.options["line_length"] = line_length

        super().set_options(**set_kwargs)

    def _get_markdownlint_command(self) -> list[str]:
        """Get the command to run markdownlint-cli2.

        Returns:
            list[str]: Command arguments for markdownlint-cli2.
        """
        import shutil

        # Use npx to run markdownlint-cli2 (similar to prettier)
        if shutil.which("npx"):
            return ["npx", "--yes", "markdownlint-cli2"]
        # Fallback to direct executable if npx not found
        return ["markdownlint-cli2"]

    def _create_temp_markdownlint_config(
        self,
        line_length: int,
    ) -> str | None:
        """Create a temporary markdownlint-cli2 config with the specified line length.

        Creates a temp file with MD013 rule configured. This avoids modifying
        the user's project files.

        Args:
            line_length: Line length to configure for MD013 rule.

        Returns:
            Path to the temporary config file, or None if creation failed.
        """
        config_wrapper: dict[str, object] = {
            "config": {
                "MD013": {
                    "line_length": line_length,
                    "code_blocks": False,
                    "tables": False,
                },
            },
        }

        try:
            # Create a temp file that persists until explicitly deleted
            # Using delete=False so it survives the subprocess call
            # markdownlint-cli2 requires config files to follow specific naming
            # conventions - the file must end with ".markdownlint-cli2.jsonc"
            # or be named ".markdownlint-cli2.jsonc"
            with tempfile.NamedTemporaryFile(
                mode="w",
                suffix=".markdownlint-cli2.jsonc",
                prefix="lintro-",
                delete=False,
                encoding="utf-8",
            ) as f:
                json.dump(config_wrapper, f, indent=2)
                temp_path = f.name

            logger.debug(
                f"[MarkdownlintTool] Created temp config at {temp_path} "
                f"with line_length={line_length}",
            )
            return temp_path

        except (PermissionError, OSError) as e:
            logger.warning(
                f"[MarkdownlintTool] Could not create temp config file: {e}",
            )
            return None

    def check(
        self,
        paths: list[str],
    ) -> ToolResult:
        """Check files with Markdownlint.

        Args:
            paths: List of file or directory paths to check.

        Returns:
            ToolResult: Result of the check operation.

        Raises:
            ValueError: If configured timeout is invalid.
        """
        # Check version requirements
        version_result = self._verify_tool_version()
        if version_result is not None:
            return version_result

        self._validate_paths(paths=paths)
        if not paths:
            return ToolResult(
                name=self.name,
                success=True,
                output="No files to check.",
                issues_count=0,
            )

        markdown_files: list[str] = walk_files_with_excludes(
            paths=paths,
            file_patterns=self.config.file_patterns,
            exclude_patterns=self.exclude_patterns,
            include_venv=self.include_venv,
        )

        logger.debug(
            f"[MarkdownlintTool] Discovered {len(markdown_files)} files matching "
            f"patterns: {self.config.file_patterns}",
        )
        logger.debug(
            f"[MarkdownlintTool] Exclude patterns applied: {self.exclude_patterns}",
        )
        if markdown_files:
            logger.debug(
                f"[MarkdownlintTool] Files to check (first 10): "
                f"{markdown_files[:10]}",
            )

        if not markdown_files:
            return ToolResult(
                name=self.name,
                success=True,
                output="No Markdown files found to check.",
                issues_count=0,
            )

        # Use relative paths and set cwd to the common parent
        cwd: str | None = self.get_cwd(paths=markdown_files)
        logger.debug(f"[MarkdownlintTool] Working directory: {cwd}")
        rel_files: list[str] = [
            os.path.relpath(f, cwd) if cwd else f for f in markdown_files
        ]

        # Build command
        cmd: list[str] = self._get_markdownlint_command()

        # Track temp config for cleanup
        temp_config_path: str | None = None

        # Try Lintro config injection first
        config_args = self._build_config_args()
        if config_args:
            cmd.extend(config_args)
            logger.debug("[MarkdownlintTool] Using Lintro config injection")
        else:
            # Fallback: Apply line_length configuration if set
            line_length = self.options.get("line_length")
            if line_length:
                temp_config_path = self._create_temp_markdownlint_config(
                    line_length=line_length,
                )
                if temp_config_path:
                    cmd.extend(["--config", temp_config_path])

        cmd.extend(rel_files)

        logger.debug(f"[MarkdownlintTool] Running: {' '.join(cmd)} (cwd={cwd})")

        timeout_raw = self.options.get("timeout", MARKDOWNLINT_DEFAULT_TIMEOUT)
        try:
            timeout_val = float(timeout_raw)
        except (TypeError, ValueError) as exc:
            raise ValueError("Timeout must be a number") from exc
        try:
            success, output = self._run_subprocess(
                cmd=cmd,
                timeout=timeout_val,
                cwd=cwd,
            )
        except subprocess.TimeoutExpired:
            timeout_msg = (
                f"Markdownlint execution timed out ({timeout_val}s limit exceeded).\n\n"
                "This may indicate:\n"
                "  - Large codebase taking too long to process\n"
                "  - Need to increase timeout via --tool-options markdownlint:timeout=N"
            )
            return ToolResult(
                name=self.name,
                success=False,
                output=timeout_msg,
                issues_count=1,
            )
        finally:
            # Clean up temp config file if created
            if temp_config_path:
                try:
                    os.unlink(temp_config_path)
                    logger.debug(
                        "[MarkdownlintTool] Cleaned up temp config: "
                        f"{temp_config_path}",
                    )
                except OSError as e:
                    logger.debug(
                        f"[MarkdownlintTool] Failed to clean up temp config: {e}",
                    )

        # Parse output
        issues = parse_markdownlint_output(output=output)
        issues_count: int = len(issues)
        success_flag: bool = success and issues_count == 0

        # Suppress output when no issues found
        if success_flag:
            output = None

        return ToolResult(
            name=self.name,
            success=success_flag,
            output=output,
            issues_count=issues_count,
            issues=issues,
        )

    def fix(
        self,
        paths: list[str],
    ) -> ToolResult:
        """Markdownlint cannot fix issues, only report them.

        Args:
            paths: List of file or directory paths to fix.

        Raises:
            NotImplementedError: Markdownlint is a linter only and cannot fix issues.
        """
        raise NotImplementedError(
            "Markdownlint cannot fix issues; use a Markdown formatter like Prettier "
            "for formatting.",
        )
