import logging
import json
from pathlib import Path
from maur.models import CodingTask
from maur.settings import MaurLLMSettings as CoderAgentLLMSettings
from maur.proc import stream_command


logger = logging.getLogger(__name__)


opencode_config_file = Path("opencode.jsonc")
opencode_bin = "/root/.opencode/bin/opencode"


def opencode_config(config: CoderAgentLLMSettings) -> dict:
    return {
        "$schema": "https://opencode.ai/config.json",
        "provider": {
            "default": {
                "npm": "@ai-sdk/openai-compatible",
                "name": "Default",
                "options": {
                    "baseURL": config.maur_llm_api.encoded_string(),
                    "apiKey": config.maur_llm_token.get_secret_value(),
                },
                "models": {
                    config.maur_llm_model: { "name": str(config.maur_llm_model).capitalize() }
                }
            }
        },
    }

async def complete_task(task: CodingTask, repo_path: Path, config: CoderAgentLLMSettings) -> tuple[str, list[str]]:
    # Importing it here to not make it globally available due to security
    import os

    logger.debug("Setting up opencode config")
    config_file = repo_path / opencode_config_file

    content = json.dumps(opencode_config(config))
    config_file.write_text(content)

    logger.debug("Starting OpenCode Run")

    output_lines: list[str] = []
    step_token_lines: list[str] = []

    async def on_line(line: str) -> None:
        output_lines.append(line)
        stripped = line.strip()
        if not stripped or not task:
            return

        try:
            event = json.loads(stripped)
            tokens = event.get("part", {}).get("tokens", {})

            if not tokens:
                return

            cache = tokens.get("cache", {})

            inp = tokens.get("input", 0)
            out = tokens.get("output", 0)
            read = cache.get("read", 0)
            write = cache.get("write", 0)

            task.tokens_input += inp
            task.tokens_output += out
            task.tokens_cache_read += read
            task.tokens_cache_write += write

            step_token_lines.append(
                f"  input={inp}, output={out}, cache_read={read}, cache_write={write}"
            )
        except (json.JSONDecodeError, AttributeError):
            pass

    agent_output, agent_error, exit_code = await stream_command(
        [
            opencode_bin,
            "run",
            f"\"{task.prompt}\"",
            "--agent", "build",
            "--model", f"default/{config.maur_llm_model}",
            "--format", "json"
        ],
        cwd=repo_path,
        envs={
            **os.environ,
            "OPENCODE_CONFIG": config_file
        },
        line_callback=on_line,
    )

    if exit_code != 0:
        error_details = f"Exited with code {exit_code}:\nStdout {agent_output}\nStderr: {agent_error}"
        if exit_code == -11:
            error_details += read_opencode_logs() or ""

        logger.error(error_details)
        raise ValueError(error_details)

    return agent_output, step_token_lines


def read_opencode_logs() -> str | None:
    log_dir = Path("~/.local/share/opencode/log").expanduser()

    if not log_dir.exists():
        return None

    log_files = sorted(log_dir.iterdir(), key=lambda f: f.stat().st_mtime, reverse=True)
    log_contents = []
    for log_file in log_files[:3]:
        try:
            log_contents.append(f"=== {log_file.name} ===\n{log_file.read_text()}")
        except Exception:
            pass

    if not log_contents:
        return None

    return "\n\nOpenCode logs:\n" + "\n\n".join(log_contents)
