from datetime import UTC, datetime
from types import SimpleNamespace
from unittest.mock import AsyncMock
from uuid import uuid4

import pytest
from src.core.security.encryption import encrypt_value
from src.domain.edge_functions import cf_runtime
from src.domain.edge_functions.service import EdgeFunctionsService


def _svc(**overrides) -> EdgeFunctionsService:
    defaults = dict(
        library_repo=AsyncMock(),
        library_version_repo=AsyncMock(),
        fn_repo=AsyncMock(),
        version_repo=AsyncMock(),
        webhook_repo=AsyncMock(),
        runtime_repo=AsyncMock(),
        token_repo=AsyncMock(),
        project_repo=AsyncMock(),
        connector_repo=AsyncMock(),
        cloud_client=AsyncMock(),
    )
    defaults.update(overrides)
    return EdgeFunctionsService(**defaults)


@pytest.mark.asyncio
async def test_ensure_function_token_creates_and_pushes_when_missing() -> None:
    token_repo = AsyncMock()
    token_repo.get.return_value = None
    cloud = AsyncMock()
    svc = _svc(token_repo=token_repo, cloud_client=cloud)

    project_id = uuid4()
    token = await svc._ensure_function_token(project_id)

    assert token
    token_repo.create.assert_awaited_once()
    cloud.push_function_token.assert_awaited_once_with(project_id=str(project_id), token=token)


@pytest.mark.asyncio
async def test_ensure_function_token_reuses_existing_without_repush() -> None:
    existing_token = "already-issued-token"
    token_repo = AsyncMock()
    token_repo.get.return_value = SimpleNamespace(token_enc=encrypt_value(existing_token))
    cloud = AsyncMock()
    svc = _svc(token_repo=token_repo, cloud_client=cloud)

    token = await svc._ensure_function_token(uuid4())

    assert token == existing_token
    token_repo.create.assert_not_called()
    cloud.push_function_token.assert_not_called()


@pytest.mark.asyncio
async def test_push_function_deploys_script_and_sends_complete_metadata(monkeypatch) -> None:
    project_id, workspace_id, fn_id = uuid4(), uuid4(), uuid4()
    project_repo = AsyncMock()
    project_repo.get_by_id.return_value = SimpleNamespace(id=project_id, workspace_id=workspace_id)
    token_repo = AsyncMock()
    token_repo.get.return_value = SimpleNamespace(token_enc=encrypt_value("scoped-token"))
    cloud = AsyncMock()

    deploy_mock = AsyncMock(return_value="fn-abc123-ledger-post-transaction")
    monkeypatch.setattr(cf_runtime, "deploy_function_script", deploy_mock)

    svc = _svc(project_repo=project_repo, token_repo=token_repo, cloud_client=cloud)

    fn = SimpleNamespace(
        id=fn_id,
        slug="ledger-post-transaction",
        name="Post Ledger Transaction",
        runtime="js",
        trigger_type="http",
        trigger_config={},
        access_policy="authenticated",
        library_fn_id=None,
        secret_refs=None,
        status="active",
        code_snapshot="export default { fetch() {} };",
        active_version_id=None,
        pack_slug=None,
        pack_version=None,
    )
    ver = SimpleNamespace(version_number=1, code_bundle_ref=None)

    await svc._push_function(project_id, fn, ver)

    deploy_mock.assert_awaited_once()
    call_kwargs = deploy_mock.await_args.kwargs
    assert call_kwargs["project_id"] == project_id
    assert call_kwargs["workspace_id"] == workspace_id
    assert call_kwargs["project_token"] == "scoped-token"

    cloud.deploy_function.assert_awaited_once()
    push_kwargs = cloud.deploy_function.await_args.kwargs
    payload = push_kwargs["payload"]
    assert payload["id"] == str(fn_id)
    assert payload["workspace_id"] == str(workspace_id)
    assert payload["worker_name"] == "fn-abc123-ledger-post-transaction"
    assert payload["active_version"] == "1"
    assert payload["status"] == "active"


@pytest.mark.asyncio
async def test_push_function_injects_required_connector_credentials(monkeypatch) -> None:
    project_id, workspace_id, fn_id, lib_id, connector_id = (
        uuid4(),
        uuid4(),
        uuid4(),
        uuid4(),
        uuid4(),
    )
    project_repo = AsyncMock()
    project_repo.get_by_id.return_value = SimpleNamespace(id=project_id, workspace_id=workspace_id)
    token_repo = AsyncMock()
    token_repo.get.return_value = SimpleNamespace(token_enc=encrypt_value("scoped-token"))
    library_repo = AsyncMock()
    library_repo.get_by_id.return_value = SimpleNamespace(required_integrations=["stripe"])
    connector_repo = AsyncMock()
    connector_repo.get_connector_by_key.return_value = SimpleNamespace(id=connector_id)
    connector_repo.list_connections_for_project.return_value = [
        SimpleNamespace(
            connector_id=connector_id,
            status="active",
            config_json={},
            secrets_enc={"secret_key": encrypt_value("sk_live_abc")},
            created_at=datetime.now(UTC),
        )
    ]

    deploy_mock = AsyncMock(return_value="fn-abc123-stripe-create-checkout")
    monkeypatch.setattr(cf_runtime, "deploy_function_script", deploy_mock)

    svc = _svc(
        project_repo=project_repo,
        token_repo=token_repo,
        library_repo=library_repo,
        connector_repo=connector_repo,
    )

    fn = SimpleNamespace(
        id=fn_id,
        slug="stripe-create-checkout",
        name="Create Stripe Checkout",
        runtime="js",
        trigger_type="http",
        trigger_config={},
        access_policy="authenticated",
        library_fn_id=lib_id,
        secret_refs=None,
        status="active",
        code_snapshot="export default { fetch() {} };",
        active_version_id=None,
        pack_slug=None,
        pack_version=None,
    )

    ver = SimpleNamespace(version_number=1, code_bundle_ref=None)
    await svc._push_function(project_id, fn, ver)

    deploy_mock.assert_awaited_once()
    call_kwargs = deploy_mock.await_args.kwargs
    assert call_kwargs["extra_secrets"] == {"STRIPE_SECRET_KEY": "sk_live_abc"}
    assert call_kwargs["extra_env_vars"] == {}


@pytest.mark.asyncio
async def test_push_function_skips_script_deploy_when_no_code_snapshot(monkeypatch) -> None:
    project_id, workspace_id = uuid4(), uuid4()
    project_repo = AsyncMock()
    project_repo.get_by_id.return_value = SimpleNamespace(id=project_id, workspace_id=workspace_id)
    cloud = AsyncMock()

    deploy_mock = AsyncMock()
    monkeypatch.setattr(cf_runtime, "deploy_function_script", deploy_mock)

    svc = _svc(project_repo=project_repo, cloud_client=cloud)

    fn = SimpleNamespace(
        id=uuid4(),
        slug="custom-fn",
        name="Custom",
        runtime="js",
        trigger_type="http",
        trigger_config={},
        access_policy="authenticated",
        library_fn_id=None,
        secret_refs=None,
        status="active",
        code_snapshot=None,
        active_version_id=None,
        pack_slug=None,
        pack_version=None,
    )

    await svc._push_function(project_id, fn, None)

    deploy_mock.assert_not_called()
    cloud.deploy_function.assert_awaited_once()
    payload = cloud.deploy_function.await_args.kwargs["payload"]
    assert payload["worker_name"]
