"""Test execution lifecycle."""

from __future__ import annotations

import sys
import time
from datetime import datetime, timezone

import threading

from opencypher_compliance import __version__
from opencypher_compliance.adapters import CypherAdapter, QueryResult, get_adapter
from opencypher_compliance.catalog import load_catalog
from opencypher_compliance.comparator import compare_result
from opencypher_compliance.config import load_config

PROBE_TIMEOUT_SEC = 10


def _execute_with_timeout(adapter: CypherAdapter, query: str, timeout_sec: int = PROBE_TIMEOUT_SEC):
    """Execute a query with a timeout. Raises TimeoutError if it hangs."""
    result_holder = []
    error_holder = []

    def target():
        try:
            result_holder.append(adapter.execute(query))
        except Exception as e:
            error_holder.append(e)

    thread = threading.Thread(target=target, daemon=True)
    thread.start()
    thread.join(timeout=timeout_sec)

    if thread.is_alive():
        raise TimeoutError(
            f"Connection probe timed out after {timeout_sec}s — "
            f"is the database running and accepting connections?"
        )
    if error_holder:
        raise error_holder[0]
    return result_holder[0] if result_holder else None


def _run_test(adapter: CypherAdapter, test: dict, verbose: bool = False) -> dict:
    """Execute a single test and return the result dict."""
    result_entry = {
        "element": test["name"],
        "type": test["type"],
    }

    start = time.monotonic()

    # Setup
    try:
        for setup_q in test.get("setup", []):
            adapter.execute(setup_q)
    except Exception as e:
        result_entry["result"] = "fail"
        result_entry["error"] = f"Setup error: {e}"
        result_entry["duration_ms"] = int((time.monotonic() - start) * 1000)
        # Try cleanup on setup failure
        try:
            adapter.cleanup()
        except Exception:
            pass
        return result_entry

    # Execute
    query_result = None
    query_error = None
    try:
        query_result = adapter.execute(test["query"])
    except Exception as e:
        query_error = e

    # Compare
    comparison = compare_result(
        query_result or QueryResult(),
        test,
        error=query_error,
    )

    result_entry["result"] = "pass" if comparison["passed"] else "fail"
    if not comparison["passed"]:
        if query_error:
            result_entry["error"] = f"Query error: {query_error}"
        elif comparison["reason"]:
            result_entry["error"] = comparison["reason"]

    result_entry["duration_ms"] = int((time.monotonic() - start) * 1000)

    # Teardown
    teardown_failed = False
    try:
        for teardown_q in test.get("teardown", []):
            adapter.execute(teardown_q)
    except Exception:
        teardown_failed = True

    if teardown_failed:
        try:
            adapter.cleanup()
        except Exception:
            pass

    return result_entry


def run_compliance(
    config: dict | None = None,
    config_path: str | None = None,
    catalog_dir: str | None = None,
    tags: list[str] | None = None,
    types: list[str] | None = None,
    verbose: bool = False,
    fail_fast: bool = False,
    adapter_instance: CypherAdapter | None = None,
    cli_overrides: dict | None = None,
) -> dict:
    """Run the full compliance test suite.

    Args:
        config: Pre-loaded config dict (alternative to config_path).
        config_path: Path to config.yaml.
        catalog_dir: Path to catalog directory (None = built-in).
        tags: Filter tests by tags.
        types: Filter by element type.
        verbose: Print progress to stderr.
        fail_fast: Stop on first failure.
        adapter_instance: Pre-configured adapter (for testing).

    Returns:
        Full results dict with metadata, summary_by_type, and results.
    """
    # Load config
    if config is None:
        config = load_config(config_path, cli_overrides=cli_overrides)

    db_config = config.get("database", {})

    # Load catalog
    tests = load_catalog(catalog_dir)

    # Filter tests
    if tags:
        tests = [t for t in tests if any(tag in t.get("tags", []) for tag in tags)]
    if types:
        tests = [t for t in tests if t["type"] in types]

    # Get adapter
    if adapter_instance is None:
        adapter = get_adapter(db_config["adapter"])
        host = db_config.get("host", "localhost")
        port = db_config.get("port", 7687)
        # Connect with fail-early error
        try:
            adapter.connect(
                host=host,
                port=port,
                auth=db_config.get("auth") or {},
                **(db_config.get("options") or {}),
            )
        except Exception as e:
            raise ConnectionError(
                f"Cannot connect to {db_config.get('name', db_config['adapter'])} "
                f"at {host}:{port} — {e}"
            ) from e
        # Probe connectivity with a trivial query (with timeout)
        try:
            _execute_with_timeout(adapter, "RETURN 1", timeout_sec=PROBE_TIMEOUT_SEC)
        except Exception as e:
            try:
                adapter.close()
            except Exception:
                pass
            raise ConnectionError(
                f"Cannot connect to {db_config.get('name', db_config['adapter'])} "
                f"at {host}:{port} — {e}"
            ) from e

    else:
        adapter = adapter_instance

    # Pre-run cleanup
    pre_run = db_config.get("pre_run")
    if pre_run:
        try:
            adapter.execute(pre_run)
        except Exception:
            try:
                adapter.cleanup()
            except Exception:
                pass

    # Get database version
    db_version = adapter.get_version()

    # Run tests
    results = []
    timestamp = datetime.now(timezone.utc).isoformat()

    if verbose:
        print(f"opencypher-compliance v{__version__}", file=sys.stderr)
        print(f"Target: {db_config.get('name', 'Unknown')} ({db_config.get('adapter')}://{db_config.get('host', 'localhost')}:{db_config.get('port', '')})", file=sys.stderr)
        print(f"Catalog: {len(tests)} tests", file=sys.stderr)
        print("\nRunning tests...", file=sys.stderr)

    for test in tests:
        entry = _run_test(adapter, test, verbose=verbose)
        results.append(entry)

        if verbose:
            status = "PASS" if entry["result"] == "pass" else "FAIL"
            line = f"  [{status}] {entry['element']} ({entry['type']})"
            line = f"{line:<55} {entry['duration_ms']}ms"
            print(line, file=sys.stderr)
            if entry.get("error"):
                print(f"         {entry['error']}", file=sys.stderr)

        if fail_fast and entry["result"] == "fail":
            break

    # Post-run cleanup
    post_run = db_config.get("post_run")
    if post_run:
        try:
            adapter.execute(post_run)
        except Exception:
            try:
                adapter.cleanup()
            except Exception:
                pass

    # Close adapter (only if we created it)
    if adapter_instance is None:
        try:
            adapter.close()
        except Exception:
            pass

    # Build summary
    passed = sum(1 for r in results if r["result"] == "pass")
    failed = sum(1 for r in results if r["result"] == "fail")
    total = len(results)
    pass_rate = f"{(passed / total * 100):.2f}%" if total > 0 else "0.00%"

    # Summary by type
    summary_by_type = {}
    for r in results:
        t = r["type"]
        if t not in summary_by_type:
            summary_by_type[t] = {"total": 0, "passed": 0, "failed": 0}
        summary_by_type[t]["total"] += 1
        if r["result"] == "pass":
            summary_by_type[t]["passed"] += 1
        else:
            summary_by_type[t]["failed"] += 1

    if verbose:
        print(f"\nResults: {passed} passed, {failed} failed ({pass_rate})", file=sys.stderr)

    return {
        "metadata": {
            "database": db_config.get("name", "Unknown"),
            "adapter": db_config.get("adapter"),
            "host": db_config.get("host", "localhost"),
            "port": db_config.get("port"),
            "database_version": db_version,
            "spec_version": "9",
            "timestamp": timestamp,
            "catalog_version": __version__,
            "total_tests": total,
            "passed": passed,
            "failed": failed,
            "pass_rate": pass_rate,
        },
        "summary_by_type": summary_by_type,
        "results": results,
    }
