import os
import time
import subprocess
import pytest
import httpx
from datetime import datetime

from dotenv import load_dotenv

# Try to find .env in current or parent dirs
env_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".env"))
if os.path.exists(env_path):
    load_dotenv(dotenv_path=env_path)
else:
    load_dotenv() # Fallback

# These are resolved at fixture time to pick up dynamic env vars from run_integration_tests.sh
BASE_URL = os.getenv("SYNC_TEST_BASE_URL", "http://127.0.0.1:8002/api/v1")
# Primary admin for integration tests: always try the FIRST one in the list
_admins = os.getenv("SUPER_ADMINS", "axieyangb@gmail.com").split(',')
ADMIN_EMAIL = _admins[0].strip()
ADMIN_PASSWORD = os.getenv("CORTEX_ADMIN_PASSWORD", "admin")
NODE_1 = os.getenv("SYNC_TEST_NODE1", "test-node-1")
NODE_2 = os.getenv("SYNC_TEST_NODE2", "test-node-2")

@pytest.fixture(scope="session")
def setup_mesh_environment():
    """
    Simulates the CUJ:
    1. Login as super admin.
    2. Add API provider configurations (using env vars).
    3. Create a group.
    4. Register nodes and assign nodes to the group.
    5. Spin up node docker containers with correct tokens.
    """
    # Re-read env at fixture time (env vars may have been set by the test runner script AFTER import)
    base_url = os.getenv("SYNC_TEST_BASE_URL", BASE_URL)
    admin_email = os.getenv("SUPER_ADMINS", ADMIN_EMAIL).split(',')[0].strip()
    admin_password = os.getenv("CORTEX_ADMIN_PASSWORD", ADMIN_PASSWORD)
    
    print(f"\n[conftest] Starting Mesh Integration Setup...")
    print(f"[conftest] BASE_URL={base_url} ADMIN={admin_email}")
    client = httpx.Client(timeout=300.0)

    # 1. Login
    print(f"[conftest] Logging in as {admin_email} (password: {admin_password}) at {base_url} ...")
    # NOTE: The Hub uses /users/login/local
    login_data = {
        "email": admin_email,
        "password": admin_password
    }
    r = client.post(f"{base_url}/users/login/local", json=login_data)
    assert r.status_code == 200, f"Login failed: {r.text}"
    
    user_id = r.json().get("user_id")
    assert user_id, "No user_id found in local login response."
    
    os.environ["SYNC_TEST_USER_ID"] = user_id
    client.headers.update({
        "X-User-ID": user_id
    })

    # 2. Add API Providers and Configure LLM RBAC
    print("[conftest] Configuring LLM provider and grouping...")
    
    # Enable Gemini securely
    prefs_payload = {
        "llm": {
            "active_provider": "gemini",
            "providers": {
                "gemini": {
                    "api_key": os.getenv("GEMINI_API_KEY", ""),
                    "model": "gemini-3-flash-preview"
                }
            }
        },
        "tts": {}, "stt": {}, "statuses": {}
    }
    r_config = client.put(f"{base_url}/users/me/config", json=prefs_payload)
    assert r_config.status_code == 200, f"Failed to configure LLM provider: {r_config.text}"
    
    # Establish a Group securely provisioned for AI Usage
    group_payload = {
        "name": "Integration Default Group",
        "description": "Global RBAC group for all integration tasks",
        "policy": {"llm": ["gemini"]}
    }
    r_group = client.post(f"{base_url}/users/admin/groups", json=group_payload)
    
    if r_group.status_code == 409:
        r_groups = client.get(f"{base_url}/users/admin/groups")
        target_group = next(g for g in r_groups.json() if g["name"] == group_payload["name"])
        group_id = target_group["id"]
        # Update policy to ensure it remains clean
        client.put(f"{base_url}/users/admin/groups/{group_id}", json=group_payload)
    else:
        group_id = r_group.json().get("id")
        
    # Bind the admin testing account into this fully capable RBAC group
    client.put(f"{base_url}/users/admin/users/{user_id}/group", json={"group_id": group_id})

    # 3. Register Nodes
    print("[conftest] Registering test nodes...")
    node_1 = os.getenv("SYNC_TEST_NODE1", NODE_1)
    node_2 = os.getenv("SYNC_TEST_NODE2", NODE_2)
    tokens = {}
    for node_id in [node_1, node_2]:
        payload = {
            "node_id": node_id,
            "display_name": f"Integration {node_id}",
            "is_active": True,
            "skill_config": {"shell": {"enabled": True}, "sync": {"enabled": True}}
        }
        r_node = client.post(
            f"{base_url}/nodes/admin",
            params={"admin_id": user_id},
            json=payload
        )
        
        # If node already exists, delete it and recreate to obtain a fresh token
        if r_node.status_code in (400, 409):
            client.delete(f"{base_url}/nodes/admin/{node_id}", params={"admin_id": user_id})
            r_node = client.post(f"{base_url}/nodes/admin", params={"admin_id": user_id}, json=payload)

        assert r_node.status_code == 200, f"Node registration failed: {r_node.text}"
        tokens[node_id] = r_node.json().get("invite_token")

    # 4. Add Group & Assign Permission (optional - tests use the user_id that registered it for now, 
    # but per CUJ we can mimic group creation)
    # 4. Ensure nodes are assigned to the group the user is in
    print("[conftest] Ensuring node access for group...")
    # Give the primary group access to nodes
    for node_id in [node_1, node_2]:
        client.post(
            f"{base_url}/nodes/admin/{node_id}/access",
            params={"admin_id": user_id},
            json={
                "group_id": group_id,
                "access_level": "use"
            }
        )
        
    updated_prefs = {
        "default_node_ids": [node_1]
    }
    client.patch(
        f"{base_url}/nodes/preferences",
        params={"user_id": user_id},
        json=updated_prefs
    )

    # 5. Start Node Processes
    is_docker_disabled = os.getenv("SKIP_DOCKER_NODES", "true").lower() == "true"
    node_processes = []
    
    if not is_docker_disabled:
        print("[conftest] Starting local docker node containers...")
        network = "cortex-hub_default"
        subprocess.run(["docker", "rm", "-f", node_1, node_2], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        print("[conftest] Building agent-node image...")
        try:
            image_proc = subprocess.run(["docker", "build", "-q", "./agent-node"], capture_output=True, text=True, check=True)
            image_id = image_proc.stdout.strip()
            if not image_id:
                raise Exception("Docker build -q returned empty image ID")
        except subprocess.CalledProcessError as e:
            print(f"❌ [conftest] Docker build failed!\nSTDOUT: {e.stdout}\nSTDERR: {e.stderr}")
            raise e
        for node_id in [node_1, node_2]:
            cmd = ["docker", "run", "-d", "--name", node_id, "--network", network, "-e", f"AGENT_NODE_ID={node_id}", "-e", f"AGENT_AUTH_TOKEN={tokens[node_id]}", "-e", f"AGENT_SECRET_KEY={os.getenv('SECRET_KEY')}", "-e", "GRPC_ENDPOINT=ai-hub:50051", "-e", "HUB_URL=http://ai-hub:8000", "-e", "AGENT_TLS_ENABLED=false", image_id]
            subprocess.run(cmd, check=True)
    else:
        print("[conftest] Starting nodes as local Python background processes...")
        # Resolve URLs for local process tests (assuming hub runs on localhost outside docker)
        grpc_ep = os.getenv("TEST_GRPC_ENDPOINT", "127.0.0.1:50051")
        http_ep = os.getenv("TEST_HUB_URL", "http://127.0.0.1:8000")
        
        # Determine the agent node source directory
        agent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "agent-node"))
        if not os.path.exists(agent_dir):
             agent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "agent-node")) # Fallback
             
        for node_id in [node_1, node_2]:
            env = os.environ.copy()
            env["AGENT_NODE_ID"] = node_id
            env["AGENT_AUTH_TOKEN"] = tokens[node_id]
            env["AGENT_SECRET_KEY"] = os.getenv('SECRET_KEY', 'integration-secret-key-123')
            env["GRPC_ENDPOINT"] = grpc_ep
            env["HUB_URL"] = http_ep
            env["AGENT_TLS_ENABLED"] = "false"
            env["PYTHONPATH"] = f"{agent_dir}/src"
            env["PYTHONUNBUFFERED"] = "1"
            env["CORTEX_SYNC_DIR"] = f"/tmp/cortex-sync-{node_id}"
            env["MAX_SKILL_WORKERS"] = "1"  # Minimize thread usage on resource-constrained environments
            
            log_file = open(f"/tmp/{node_id}_pytest.log", "w")
            import sys
            proc = subprocess.Popen(
                [sys.executable, "-m", "agent_node.main"],
                env=env,
                cwd=agent_dir,
                stdout=log_file,
                stderr=log_file
            )
            node_processes.append(proc)

    print("[conftest] Waiting for nodes to connect to mesh...")
    time.sleep(15)

    client.close()

    yield node_processes

    # 6. Teardown
    print("\n[conftest] Tearing down nodes...")
    if not is_docker_disabled:
        subprocess.run(["docker", "rm", "-f", node_1, node_2], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    else:
        for proc in node_processes:
            proc.terminate()
            proc.wait()

@pytest.fixture(autouse=True)
def run_around_tests(setup_mesh_environment):
    """Ensure setup runs for all tests."""
    yield

def pytest_addoption(parser):
    parser.addoption(
        "--runslow", action="store_true", default=False, help="run slow tests"
    )

def pytest_collection_modifyitems(config, items):
    import os
    try:
        run_slow = config.getoption("--runslow")
    except ValueError:
        run_slow = False
        
    if run_slow or os.getenv("RUN_SLOW_TESTS", "false").lower() == "true":
        # --runslow given in cli or RUN_SLOW_TESTS env var set: do not skip slow tests
        return
    skip_slow = pytest.mark.skip(reason="need --runslow option to run")
    for item in items:
        if "slow" in item.keywords:
            item.add_marker(skip_slow)
