# grantflow/tests/test_integration_readiness_startup.py

import asyncio
from types import SimpleNamespace

import pytest

from grantflow.api import runtime_state
from grantflow.api.app import app
from grantflow.api.runtime_service import (
    _app_lifespan,
    _validate_api_key_startup_security,
    _validate_persistent_store_startup_security,
    _validate_runtime_compatibility_configuration,
    _validate_store_backend_alignment,
    _validate_tenant_authz_configuration,
)
from grantflow.core.config import config


def test_store_backend_alignment_validation_detects_job_hitl_mismatch(monkeypatch):
    class FakeJobStore:
        db_path = "/tmp/grantflow_state.db"

    class FakeHitlManager:
        _use_sqlite = False

    monkeypatch.setattr(runtime_state, "JOB_STORE", FakeJobStore())
    monkeypatch.setattr(runtime_state, "hitl_manager", FakeHitlManager())
    with pytest.raises(RuntimeError, match="GRANTFLOW_JOB_STORE"):
        _validate_store_backend_alignment()


def test_store_backend_alignment_validation_allows_matching_backends(monkeypatch):
    class FakeJobStore:
        db_path = "/tmp/grantflow_state.db"

    class FakeHitlManager:
        _use_sqlite = True

    monkeypatch.setattr(runtime_state, "JOB_STORE", FakeJobStore())
    monkeypatch.setattr(runtime_state, "hitl_manager", FakeHitlManager())
    _validate_store_backend_alignment()


def test_store_backend_alignment_validation_runs_at_startup(monkeypatch):
    class FakeJobStore:
        db_path = "/tmp/grantflow_state.db"

    class FakeHitlManager:
        _use_sqlite = False

    monkeypatch.setattr(runtime_state, "JOB_STORE", FakeJobStore())
    monkeypatch.setattr(runtime_state, "hitl_manager", FakeHitlManager())

    async def _start_lifespan():
        async with _app_lifespan(app):
            return None

    with pytest.raises(RuntimeError, match="GRANTFLOW_HITL_STORE"):
        asyncio.run(_start_lifespan())


def test_tenant_authz_configuration_validation_detects_strict_misconfig(monkeypatch):
    monkeypatch.setenv("GRANTFLOW_TENANT_AUTHZ_ENABLED", "true")
    monkeypatch.delenv("GRANTFLOW_ALLOWED_TENANTS", raising=False)
    monkeypatch.delenv("AIDGRAPH_ALLOWED_TENANTS", raising=False)
    monkeypatch.setenv("GRANTFLOW_TENANT_AUTHZ_CONFIGURATION_POLICY_MODE", "strict")

    with pytest.raises(RuntimeError, match="GRANTFLOW_TENANT_AUTHZ_ENABLED=true"):
        _validate_tenant_authz_configuration()


def test_tenant_authz_configuration_validation_allows_strict_with_allowlist(monkeypatch):
    monkeypatch.setenv("GRANTFLOW_TENANT_AUTHZ_ENABLED", "true")
    monkeypatch.setenv("GRANTFLOW_ALLOWED_TENANTS", "tenant_alpha")
    monkeypatch.setenv("GRANTFLOW_TENANT_AUTHZ_CONFIGURATION_POLICY_MODE", "strict")

    _validate_tenant_authz_configuration()


def test_tenant_authz_configuration_validation_detects_default_tenant_not_allowed(monkeypatch):
    monkeypatch.setenv("GRANTFLOW_TENANT_AUTHZ_ENABLED", "true")
    monkeypatch.setenv("GRANTFLOW_ALLOWED_TENANTS", "tenant_alpha")
    monkeypatch.setenv("GRANTFLOW_DEFAULT_TENANT", "tenant_beta")
    monkeypatch.setenv("GRANTFLOW_TENANT_AUTHZ_CONFIGURATION_POLICY_MODE", "strict")

    with pytest.raises(RuntimeError, match="GRANTFLOW_DEFAULT_TENANT"):
        _validate_tenant_authz_configuration()


def test_tenant_authz_configuration_validation_runs_at_startup(monkeypatch):
    monkeypatch.setenv("GRANTFLOW_TENANT_AUTHZ_ENABLED", "true")
    monkeypatch.delenv("GRANTFLOW_ALLOWED_TENANTS", raising=False)
    monkeypatch.delenv("AIDGRAPH_ALLOWED_TENANTS", raising=False)
    monkeypatch.setenv("GRANTFLOW_TENANT_AUTHZ_CONFIGURATION_POLICY_MODE", "strict")

    async def _start_lifespan():
        async with _app_lifespan(app):
            return None

    with pytest.raises(RuntimeError, match="Tenant authz misconfiguration"):
        asyncio.run(_start_lifespan())


def test_runtime_compatibility_validation_detects_strict_misconfig(monkeypatch):
    monkeypatch.setattr(config.graph, "runtime_compatibility_policy_mode", "strict")
    monkeypatch.setattr(runtime_state, "runtime_sys", SimpleNamespace(version_info=(3, 14, 0, "final", 0)))

    with pytest.raises(RuntimeError, match="outside validated range"):
        _validate_runtime_compatibility_configuration()


def test_runtime_compatibility_validation_allows_supported_version(monkeypatch):
    monkeypatch.setattr(config.graph, "runtime_compatibility_policy_mode", "strict")
    monkeypatch.setattr(runtime_state, "runtime_sys", SimpleNamespace(version_info=(3, 11, 9, "final", 0)))

    _validate_runtime_compatibility_configuration()


def test_runtime_compatibility_validation_runs_at_startup(monkeypatch):
    monkeypatch.setattr(config.graph, "runtime_compatibility_policy_mode", "strict")
    monkeypatch.setattr(runtime_state, "runtime_sys", SimpleNamespace(version_info=(3, 14, 0, "final", 0)))

    async def _start_lifespan():
        async with _app_lifespan(app):
            return None

    with pytest.raises(RuntimeError, match="Runtime compatibility misconfiguration"):
        asyncio.run(_start_lifespan())


def test_api_key_startup_security_blocks_production_without_key(monkeypatch):
    monkeypatch.setenv("GRANTFLOW_ENV", "production")
    monkeypatch.delenv("GRANTFLOW_API_KEY", raising=False)
    monkeypatch.delenv("API_KEY", raising=False)
    monkeypatch.delenv("GRANTFLOW_REQUIRE_API_KEY_ON_STARTUP", raising=False)

    with pytest.raises(RuntimeError, match="GRANTFLOW_API_KEY"):
        _validate_api_key_startup_security()


def test_api_key_startup_security_allows_dev_without_key(monkeypatch):
    monkeypatch.setenv("GRANTFLOW_ENV", "dev")
    monkeypatch.delenv("GRANTFLOW_API_KEY", raising=False)
    monkeypatch.delenv("API_KEY", raising=False)
    monkeypatch.delenv("GRANTFLOW_REQUIRE_API_KEY_ON_STARTUP", raising=False)

    _validate_api_key_startup_security()


def test_api_key_startup_security_allows_explicit_override(monkeypatch):
    monkeypatch.setenv("GRANTFLOW_ENV", "production")
    monkeypatch.delenv("GRANTFLOW_API_KEY", raising=False)
    monkeypatch.delenv("API_KEY", raising=False)
    monkeypatch.setenv("GRANTFLOW_REQUIRE_API_KEY_ON_STARTUP", "false")

    _validate_api_key_startup_security()


def test_api_key_startup_security_runs_at_startup(monkeypatch):
    monkeypatch.setenv("GRANTFLOW_ENV", "prod")
    monkeypatch.delenv("GRANTFLOW_API_KEY", raising=False)
    monkeypatch.delenv("API_KEY", raising=False)
    monkeypatch.delenv("GRANTFLOW_REQUIRE_API_KEY_ON_STARTUP", raising=False)

    async def _start_lifespan():
        async with _app_lifespan(app):
            return None

    with pytest.raises(RuntimeError, match="Security defaults violation"):
        asyncio.run(_start_lifespan())


def test_persistent_store_startup_security_blocks_production_with_inmem(monkeypatch):
    class FakeJobStore:
        db_path = None

    class FakeHitlManager:
        _use_sqlite = False

    class FakeIngestAuditStore:
        db_path = None

    monkeypatch.setenv("GRANTFLOW_ENV", "production")
    monkeypatch.setenv("GRANTFLOW_API_KEY", "test-secret")
    monkeypatch.delenv("API_KEY", raising=False)
    monkeypatch.delenv("GRANTFLOW_REQUIRE_PERSISTENT_STORES_ON_STARTUP", raising=False)
    monkeypatch.setattr(runtime_state, "JOB_STORE", FakeJobStore())
    monkeypatch.setattr(runtime_state, "hitl_manager", FakeHitlManager())
    monkeypatch.setattr(runtime_state, "INGEST_AUDIT_STORE", FakeIngestAuditStore())

    with pytest.raises(RuntimeError, match="GRANTFLOW_JOB_STORE=inmem"):
        _validate_persistent_store_startup_security()


def test_persistent_store_startup_security_allows_dev_with_inmem(monkeypatch):
    class FakeJobStore:
        db_path = None

    class FakeHitlManager:
        _use_sqlite = False

    class FakeIngestAuditStore:
        db_path = None

    monkeypatch.setenv("GRANTFLOW_ENV", "dev")
    monkeypatch.delenv("GRANTFLOW_REQUIRE_PERSISTENT_STORES_ON_STARTUP", raising=False)
    monkeypatch.setattr(runtime_state, "JOB_STORE", FakeJobStore())
    monkeypatch.setattr(runtime_state, "hitl_manager", FakeHitlManager())
    monkeypatch.setattr(runtime_state, "INGEST_AUDIT_STORE", FakeIngestAuditStore())

    _validate_persistent_store_startup_security()


def test_persistent_store_startup_security_allows_explicit_override(monkeypatch):
    class FakeJobStore:
        db_path = None

    class FakeHitlManager:
        _use_sqlite = False

    class FakeIngestAuditStore:
        db_path = None

    monkeypatch.setenv("GRANTFLOW_ENV", "production")
    monkeypatch.setenv("GRANTFLOW_REQUIRE_PERSISTENT_STORES_ON_STARTUP", "false")
    monkeypatch.setattr(runtime_state, "JOB_STORE", FakeJobStore())
    monkeypatch.setattr(runtime_state, "hitl_manager", FakeHitlManager())
    monkeypatch.setattr(runtime_state, "INGEST_AUDIT_STORE", FakeIngestAuditStore())

    _validate_persistent_store_startup_security()


def test_persistent_store_startup_security_allows_production_with_sqlite(monkeypatch):
    class FakeJobStore:
        db_path = "/tmp/grantflow_state.db"

    class FakeHitlManager:
        _use_sqlite = True

    class FakeIngestAuditStore:
        db_path = "/tmp/grantflow_state.db"

    monkeypatch.setenv("GRANTFLOW_ENV", "production")
    monkeypatch.setenv("GRANTFLOW_API_KEY", "test-secret")
    monkeypatch.delenv("API_KEY", raising=False)
    monkeypatch.delenv("GRANTFLOW_REQUIRE_PERSISTENT_STORES_ON_STARTUP", raising=False)
    monkeypatch.setattr(runtime_state, "JOB_STORE", FakeJobStore())
    monkeypatch.setattr(runtime_state, "hitl_manager", FakeHitlManager())
    monkeypatch.setattr(runtime_state, "INGEST_AUDIT_STORE", FakeIngestAuditStore())

    _validate_persistent_store_startup_security()


def test_persistent_store_startup_security_runs_at_startup(monkeypatch):
    class FakeJobStore:
        db_path = None

    class FakeHitlManager:
        _use_sqlite = False

    class FakeIngestAuditStore:
        db_path = None

    monkeypatch.setenv("GRANTFLOW_ENV", "prod")
    monkeypatch.setenv("GRANTFLOW_API_KEY", "test-secret")
    monkeypatch.delenv("API_KEY", raising=False)
    monkeypatch.delenv("GRANTFLOW_REQUIRE_PERSISTENT_STORES_ON_STARTUP", raising=False)
    monkeypatch.setattr(runtime_state, "JOB_STORE", FakeJobStore())
    monkeypatch.setattr(runtime_state, "hitl_manager", FakeHitlManager())
    monkeypatch.setattr(runtime_state, "INGEST_AUDIT_STORE", FakeIngestAuditStore())

    async def _start_lifespan():
        async with _app_lifespan(app):
            return None

    with pytest.raises(RuntimeError, match="persistent stores are required at startup"):
        asyncio.run(_start_lifespan())
