"""Connector catalog, authorization, and Cloud sync tests."""
from __future__ import annotations

from datetime import datetime, timezone
from uuid import uuid4

import pytest

from src.core.security.cloud_tokens import (
    create_connector_approval_token,
    decode_connector_approval_token,
)
from src.domain.connectors.models import Connector, ConnectorConnection, ConnectorPermission
from src.domain.connectors.schemas import CreateConnectorApprovalRequest
from src.domain.connectors.catalog import get_connector
from src.domain.connectors.service import ConnectorService, _permission_mode_for_create
from src.integrations.cloud_control_client import CloudControlClient


class _FakeRepo:
    def __init__(
        self,
        *,
        connector: Connector,
        connection: ConnectorConnection,
        permission: ConnectorPermission | None,
    ) -> None:
        self.connector = connector
        self.connection = connection
        self.permission = permission

    async def get_connection_for_project(self, project_id, connection_id):
        if self.connection.project_id == project_id and self.connection.id == connection_id:
            return self.connection
        return None

    async def get_connector_by_id(self, connector_id):
        if self.connector.id == connector_id:
            return self.connector
        return None

    async def get_connector_by_key(self, key):
        if self.connector.key == key:
            return self.connector
        return None

    async def get_permission(self, connection_id, action_slug):
        if self.permission and self.permission.connection_id == connection_id:
            return self.permission
        return None


class _CapturingCloudClient(CloudControlClient):
    def __init__(self) -> None:
        super().__init__(api_url="https://cloud.internal", token="token")
        self.calls: list[tuple[str, dict]] = []

    async def _put(self, path: str, payload: dict) -> dict:
        self.calls.append((path, payload))
        return {"ok": True}


def _connector_row(connector_id):
    return Connector(
        id=connector_id,
        key="stripe",
        name="Stripe",
        category="payments",
        auth_type="api_key",
        status="active",
    )


def _connection_row(workspace_id, project_id, connector_id, connection_id):
    now = datetime.now(timezone.utc)
    return ConnectorConnection(
        id=connection_id,
        connector_id=connector_id,
        workspace_id=workspace_id,
        project_id=project_id,
        name="stripe_main",
        status="active",
        auth_data_ref="connector:secret-ref",
        scopes=["checkout:create"],
        config_json={"account_id": "acct_123"},
        secrets_enc={"secret_key": "encrypted"},
        created_at=now,
        updated_at=now,
    )


@pytest.mark.anyio
async def test_connector_catalog_exposes_provider_shaped_actions(mock_db) -> None:
    svc = ConnectorService(repo=mock_db, cloud_client=CloudControlClient())

    catalog = await svc.list_catalog()

    stripe = next(item for item in catalog if item.key == "stripe")
    assert "stripe.create_checkout" in {action.slug for action in stripe.actions}
    assert "secret_key" in stripe.secret_fields

    salesforce = next(item for item in catalog if item.key == "salesforce")
    assert salesforce.config_field_defs[0].label == "Salesforce API version"
    assert "instance_url" not in salesforce.secret_fields

    ses = next(item for item in catalog if item.key == "ses")
    assert ses.category == "email"
    assert "ses.send_email" in {action.slug for action in ses.actions}
    assert "access_key_id" in ses.secret_fields
    assert "from_email" in ses.config_fields


@pytest.mark.anyio
async def test_connector_authorization_requires_approval_token() -> None:
    workspace_id = uuid4()
    project_id = uuid4()
    connector_id = uuid4()
    connection_id = uuid4()
    now = datetime.now(timezone.utc)
    permission = ConnectorPermission(
        id=uuid4(),
        connection_id=connection_id,
        action_slug="stripe.create_checkout",
        mode="ask",
        created_at=now,
        updated_at=now,
    )
    repo = _FakeRepo(
        connector=_connector_row(connector_id),
        connection=_connection_row(workspace_id, project_id, connector_id, connection_id),
        permission=permission,
    )
    svc = ConnectorService(repo=repo, cloud_client=CloudControlClient())
    approval_token, _ = create_connector_approval_token(
        workspace_id=workspace_id,
        project_id=project_id,
        connection_id=connection_id,
        action_slug="stripe.create_checkout",
    )

    denied = await svc.authorize_execution(
        workspace_id=workspace_id,
        project_id=project_id,
        connection_id=connection_id,
        action_slug="stripe.create_checkout",
        enabled_capabilities={"integrations.execute"},
    )
    allowed = await svc.authorize_execution(
        workspace_id=workspace_id,
        project_id=project_id,
        connection_id=connection_id,
        action_slug="stripe.create_checkout",
        enabled_capabilities={"integrations.execute"},
        approval_token=approval_token,
    )

    assert denied.allowed is False
    assert denied.reason_code == "approval_required"
    assert allowed.allowed is True
    assert allowed.permission.mode == "ask"


@pytest.mark.anyio
async def test_connector_authorization_rejects_invalid_approval_token() -> None:
    workspace_id = uuid4()
    project_id = uuid4()
    connector_id = uuid4()
    connection_id = uuid4()
    now = datetime.now(timezone.utc)
    permission = ConnectorPermission(
        id=uuid4(),
        connection_id=connection_id,
        action_slug="stripe.create_checkout",
        mode="ask",
        created_at=now,
        updated_at=now,
    )
    repo = _FakeRepo(
        connector=_connector_row(connector_id),
        connection=_connection_row(workspace_id, project_id, connector_id, connection_id),
        permission=permission,
    )
    svc = ConnectorService(repo=repo, cloud_client=CloudControlClient())

    denied = await svc.authorize_execution(
        workspace_id=workspace_id,
        project_id=project_id,
        connection_id=connection_id,
        action_slug="stripe.create_checkout",
        enabled_capabilities={"integrations.execute"},
        approval_token="approval_123",
    )

    assert denied.allowed is False
    assert denied.reason_code == "invalid_approval_token"


@pytest.mark.anyio
async def test_connector_authorization_enforces_required_capabilities() -> None:
    workspace_id = uuid4()
    project_id = uuid4()
    connector_id = uuid4()
    connection_id = uuid4()
    permission = ConnectorPermission(
        id=uuid4(),
        connection_id=connection_id,
        action_slug="stripe.create_checkout",
        mode="allow",
    )
    repo = _FakeRepo(
        connector=_connector_row(connector_id),
        connection=_connection_row(workspace_id, project_id, connector_id, connection_id),
        permission=permission,
    )
    svc = ConnectorService(repo=repo, cloud_client=CloudControlClient())

    denied = await svc.authorize_execution(
        workspace_id=workspace_id,
        project_id=project_id,
        connection_id=connection_id,
        action_slug="stripe.create_checkout",
        enabled_capabilities=set(),
    )

    assert denied.allowed is False
    assert denied.reason_code == "capability_not_enabled"


@pytest.mark.anyio
async def test_connector_approval_token_is_scoped() -> None:
    workspace_id = uuid4()
    project_id = uuid4()
    connector_id = uuid4()
    connection_id = uuid4()
    user_id = uuid4()
    now = datetime.now(timezone.utc)
    permission = ConnectorPermission(
        id=uuid4(),
        connection_id=connection_id,
        action_slug="stripe.create_checkout",
        mode="ask",
        created_at=now,
        updated_at=now,
    )
    repo = _FakeRepo(
        connector=_connector_row(connector_id),
        connection=_connection_row(workspace_id, project_id, connector_id, connection_id),
        permission=permission,
    )
    svc = ConnectorService(repo=repo, cloud_client=CloudControlClient())

    approval = await svc.create_approval(
        workspace_id=workspace_id,
        project_id=project_id,
        connection_id=connection_id,
        user_id=user_id,
        body=CreateConnectorApprovalRequest(action_slug="stripe.create_checkout"),
    )
    claims = decode_connector_approval_token(approval.approval_token)

    assert claims is not None
    assert claims["ws"] == str(workspace_id)
    assert claims["pid"] == str(project_id)
    assert claims["cid"] == str(connection_id)
    assert claims["act"] == "stripe.create_checkout"
    assert claims["uid"] == str(user_id)


@pytest.mark.anyio
async def test_cloud_connector_sync_uses_secret_ref_not_raw_secret() -> None:
    client = _CapturingCloudClient()

    await client.upsert_connector_connection(
        connection_id="conn_123",
        connector_key="stripe",
        workspace_id="ws_123",
        project_id="prj_123",
        name="stripe_main",
        status="active",
        auth_data_ref="connector:ws_123:prj_123:conn_123",
        scopes=[],
        config={"account_id": "acct_123"},
        permissions=[
            {
                "connection_id": "conn_123",
                "project_id": "prj_123",
                "action_slug": "stripe.create_checkout",
                "mode": "allow",
                "policy": {},
            }
        ],
    )

    _path, payload = client.calls[0]
    assert payload["auth_data_ref"] == "connector:ws_123:prj_123:conn_123"
    assert "secret_key" not in str(payload)
    assert payload["permissions"][0]["connection_id"] == "conn_123"
    assert payload["permissions"][0]["action_slug"] == "stripe.create_checkout"


def test_email_connector_send_is_allowed_by_default() -> None:
    resend = get_connector("resend")
    mailgun = get_connector("mailgun")
    ses = get_connector("ses")
    assert resend is not None and mailgun is not None and ses is not None
    assert _permission_mode_for_create(resend, {}, "resend.send_email") == "allow"
    assert _permission_mode_for_create(resend, {}, "resend.cancel_email") == "deny"
    assert _permission_mode_for_create(mailgun, {}, "mailgun.send_email") == "allow"
    assert _permission_mode_for_create(ses, {}, "ses.send_email") == "allow"
    stripe = get_connector("stripe")
    assert stripe is not None
    assert _permission_mode_for_create(stripe, {}, "stripe.create_checkout") == "deny"
