from __future__ import annotations

import pytest

from protean import BaseCommand, BaseCommandHandler, BaseEventSourcedAggregate, handle
from protean.fields import DateTime, Identifier, String


class User(BaseEventSourcedAggregate):
    email = String()
    name = String()
    password_hash = String()


class Email(BaseEventSourcedAggregate):
    email = String()
    sent_at = DateTime()


class Register(BaseCommand):
    id = Identifier()
    email = String()
    name = String()
    password_hash = String()


class Activate(BaseCommand):
    id = Identifier()
    activated_at = DateTime()


class Login(BaseCommand):
    id = Identifier()
    activated_at = DateTime()

    class Meta:
        aggregate_cls = User


class Subscribe(BaseCommand):
    """An event generated by an external system in its own stream,
    that is consumed and stored as part of the User aggregate.
    """

    id = Identifier()

    class Meta:
        stream_name = "subscriptions"


class Send(BaseCommand):
    email = String()
    sent_at = DateTime()


class Recall(BaseCommand):
    email = String()
    sent_at = DateTime()

    class Meta:
        aggregate_cls = Email
        stream_name = "recalls"


class UserCommandHandler(BaseCommandHandler):
    @handle(Register)
    def send_activation_email(self, _: Register) -> None:
        pass

    @handle(Activate)
    def provision_user(self, _: Activate) -> None:
        pass

    @handle(Login)
    def login(self, _: Login) -> None:
        pass

    @handle(Subscribe)
    def subscribe_for_notifications(self, _: Subscribe) -> None:
        pass


class EmailCommandHandler(BaseCommandHandler):
    @handle(Send)
    def send_mail(self, _: Send) -> None:
        pass

    @handle(Recall)
    def recall(self, _: Recall) -> None:
        pass


@pytest.fixture(autouse=True)
def register(test_domain):
    test_domain.register(User)
    test_domain.register(Email)
    test_domain.register(UserCommandHandler, aggregate_cls=User)
    test_domain.register(EmailCommandHandler, aggregate_cls=Email)


def test_automatic_association_of_events_with_aggregate_and_stream():
    assert Register.meta_.aggregate_cls is None
    assert Register.meta_.stream_name == "user"

    assert Activate.meta_.aggregate_cls is None
    assert Activate.meta_.stream_name == "user"

    assert Subscribe.meta_.aggregate_cls is None
    assert Subscribe.meta_.stream_name == "subscriptions"

    assert Send.meta_.aggregate_cls is None
    assert Send.meta_.stream_name == "email"

    assert Recall.meta_.aggregate_cls is Email
    assert Recall.meta_.stream_name == "recalls"
