import random
from pathlib import Path

from mixersystem.data.repository import (
    context_scope, get_context, parse_result, rel_path,
    update_session_run,
)
from mixersystem.workflows.work import work_builder as builder
from mixersystem.workflows.work.agents import work_tester as tester
from mixersystem.workflows.shared.implementation_checker import run as check_implementation

DEFAULT_MAX_TEST_ITERATIONS = 5
_CONCRETE_PROVIDERS = ["claude", "gemini", "codex"]
_SUPPORTED_PROVIDERS = {*_CONCRETE_PROVIDERS, "random"}


def _resolve_provider(provider: str) -> str:
    """Resolve provider name, picking randomly if 'random'."""
    if provider == "random":
        return random.choice(_CONCRETE_PROVIDERS)
    return provider


def _strip_status_block(text: str) -> str:
    """Remove a leading Build Status block if present."""
    header = "# Build Status\n"
    if not text.startswith(header):
        return text
    parts = text.split("\n\n", 1)
    if len(parts) == 1:
        return ""
    return parts[1]


def _upsert_build_status(*, work_path: str,
                         build_succeeded: bool,
                         tests_succeeded: bool,
                         max_test_iterations: int,
                         test_iterations: int,
                         last_test_feedback: str) -> None:
    """Write/replace a canonical build status block at top of work.md."""
    p = Path(work_path)
    raw = p.read_text() if p.is_file() else ""
    body = _strip_status_block(raw)

    build_line = "build succeeded" if build_succeeded else "build not succeeded"
    test_line = "tests succeeded" if tests_succeeded else "tests not succeeded"

    status = (
        "# Build Status\n"
        f"- {build_line}\n"
        f"- {test_line}\n"
        f"- test_iterations: {test_iterations}\n"
        f"- max_test_iterations: {max_test_iterations}\n"
    )
    cleaned_feedback = " ".join(last_test_feedback.split())
    if cleaned_feedback:
        status += f"- last_test_feedback: {cleaned_feedback}\n"
    status += "\n"

    p.write_text(status + body)


async def run(session_folder: str, lite: bool = False,
              depth: int | None = None,
              work_builder_provider: str = "claude",
              max_test_iterations: int = DEFAULT_MAX_TEST_ITERATIONS,
              instructions: str | None = None) -> None:
    """Build code from plan.md using build/test loop until pass or max iterations."""
    if max_test_iterations < 0:
        raise ValueError("max_test_iterations must be >= 0")
    if work_builder_provider not in _SUPPORTED_PROVIDERS:
        allowed = ", ".join(sorted(_SUPPORTED_PROVIDERS))
        raise ValueError(
            f"Unsupported work_builder_provider: {work_builder_provider}. "
            f"Supported providers: {allowed}."
        )

    resolved_provider = _resolve_provider(work_builder_provider)

    wf = Path(session_folder)
    wf.mkdir(parents=True, exist_ok=True)

    with context_scope(
        session_folder=str(wf.resolve()),
        lite=lite,
        depth=depth,
    ):
        await _build_and_test(
            provider=resolved_provider,
            max_test_iterations=max_test_iterations,
            instructions=instructions,
        )
        await check_implementation("work")


async def _build_and_test(*, provider: str = "claude",
                          max_test_iterations: int,
                          instructions: str | None = None) -> None:
    """Run builder + tester loop. Raises if tests never pass."""
    ctx = get_context()

    work_path = ctx.paths["work_path"]

    update_session_run(
        ctx.session_folder, "work",
        provider=provider,
        max_test_iterations=max_test_iterations,
        lite=ctx.lite,
    )

    # Initial build (run #1)
    session_id = await builder.run(
        provider=provider,
        run_index=1,
        max_test_iterations=max_test_iterations,
        instructions=instructions,
    )

    # Skip testing entirely when max_test_iterations is 0
    if max_test_iterations == 0:
        _upsert_build_status(
            work_path=work_path,
            build_succeeded=True,
            tests_succeeded=False,
            max_test_iterations=0,
            test_iterations=0,
            last_test_feedback="testing skipped (max_test_iterations=0)",
        )
        return

    # Builder<->tester loop
    passed = False
    last_test_feedback = ""
    test_iterations = 0
    for test_iteration in range(1, max_test_iterations + 1):
        test_iterations = test_iteration
        test_response = await tester.run()
        test_result = parse_result(test_response)
        last_test_feedback = test_result.notes or test_response

        if test_result.passed:
            passed = True
            break

        # Do not run another builder pass after the final allowed failed test.
        if test_iteration < max_test_iterations:
            session_id = await builder.run_resume(
                feedback=last_test_feedback,
                session_id=session_id,
                provider=provider,
                run_index=test_iteration + 1,
                max_test_iterations=max_test_iterations,
            )

    if not passed:
        _upsert_build_status(
            work_path=work_path,
            build_succeeded=False,
            tests_succeeded=False,
            max_test_iterations=max_test_iterations,
            test_iterations=test_iterations,
            last_test_feedback=last_test_feedback,
        )
        raise RuntimeError(
            "Work build failed: tester still failing after "
            f"{max_test_iterations} iterations. "
            f"Work report updated: {rel_path(work_path)}"
        )

    _upsert_build_status(
        work_path=work_path,
        build_succeeded=True,
        tests_succeeded=True,
        max_test_iterations=max_test_iterations,
        test_iterations=test_iterations,
        last_test_feedback=last_test_feedback,
    )


if __name__ == "__main__":
    from mixersystem.data.repository import run_cli
    run_cli(run)
