import json
from datetime import date

from factory import Sequence, post_generation, SubFactory
from factory.alchemy import SQLAlchemyModelFactory
from factory.fuzzy import FuzzyChoice, FuzzyText, FuzzyDate, FuzzyInteger

from lemur.database import db
from lemur.authorities.models import Authority
from lemur.certificates.models import Certificate
from lemur.destinations.models import Destination
from lemur.dns_providers.models import DnsProvider
from lemur.sources.models import Source
from lemur.notifications.models import Notification
from lemur.pending_certificates.models import PendingCertificate
from lemur.users.models import User
from lemur.roles.models import Role
from lemur.endpoints.models import Policy, Endpoint
from lemur.policies.models import RotationPolicy
from lemur.api_keys.models import ApiKey

from .vectors import (
    SAN_CERT_STR,
    SAN_CERT_KEY,
    CSR_STR,
    INTERMEDIATE_CERT_STR,
    ROOTCA_CERT_STR,
    INTERMEDIATE_KEY,
    WILDCARD_CERT_KEY,
    INVALID_CERT_STR,
)


class BaseFactory(SQLAlchemyModelFactory):
    """Base factory."""

    class Meta:
        """Factory configuration."""

        abstract = True
        sqlalchemy_session = db.session


class RotationPolicyFactory(BaseFactory):
    """Rotation Factory."""

    name = Sequence(lambda n: "policy{0}".format(n))
    days = 30

    class Meta:
        """Factory configuration."""

        model = RotationPolicy


class CertificateFactory(BaseFactory):
    """Certificate factory."""

    name = Sequence(lambda n: "certificate{0}".format(n))
    chain = INTERMEDIATE_CERT_STR
    body = SAN_CERT_STR
    private_key = SAN_CERT_KEY
    owner = "joe@example.com"
    status = FuzzyChoice(["valid", "revoked", "unknown"])
    deleted = False
    description = FuzzyText(length=128)
    active = True
    date_created = FuzzyDate(date(2016, 1, 1), date(2020, 1, 1))
    rotation_policy = SubFactory(RotationPolicyFactory)

    class Meta:
        """Factory Configuration."""

        model = Certificate

    @post_generation
    def user(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            self.user_id = extracted.id

    @post_generation
    def authority(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            self.authority_id = extracted.id

    @post_generation
    def notifications(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            for notification in extracted:
                self.notifications.append(notification)

    @post_generation
    def destinations(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            for destination in extracted:
                self.destintations.append(destination)

    @post_generation
    def replaces(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            for replace in extracted:
                self.replaces.append(replace)

    @post_generation
    def sources(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            for source in extracted:
                self.sources.append(source)

    @post_generation
    def domains(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            for domain in extracted:
                self.domains.append(domain)

    @post_generation
    def roles(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            for domain in extracted:
                self.roles.append(domain)


class CACertificateFactory(CertificateFactory):
    chain = ROOTCA_CERT_STR
    body = INTERMEDIATE_CERT_STR
    private_key = INTERMEDIATE_KEY


class InvalidCertificateFactory(CertificateFactory):
    body = INVALID_CERT_STR
    private_key = ""
    chain = ""


class AuthorityFactory(BaseFactory):
    """Authority factory."""

    name = Sequence(lambda n: "authority{0}".format(n))
    owner = "joe@example.com"
    plugin = {"slug": "test-issuer"}
    description = FuzzyText(length=128)
    authority_certificate = SubFactory(CACertificateFactory)

    class Meta:
        """Factory configuration."""

        model = Authority

    @post_generation
    def roles(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            for role in extracted:
                self.roles.append(role)


class AsyncAuthorityFactory(AuthorityFactory):
    """Async Authority factory."""

    name = Sequence(lambda n: "authority{0}".format(n))
    owner = "joe@example.com"
    plugin = {"slug": "test-issuer-async"}
    description = FuzzyText(length=128)
    authority_certificate = SubFactory(CertificateFactory)


class CryptoAuthorityFactory(AuthorityFactory):
    """Authority factory based on 'cryptography' plugin."""

    plugin = {"slug": "cryptography-issuer"}


class OptionalCNAuthorityFactory(AuthorityFactory):
    """Optional CN Authority factory."""

    name = Sequence(lambda n: "authority{0}".format(n))
    options = '[{"name": "cn_optional", "type": "boolean", "value":true, "helpMessage": "Define if CN is an optional input when issuing certificates"}]'


class DestinationFactory(BaseFactory):
    """Destination factory."""

    plugin_name = "test-destination"
    label = Sequence(lambda n: "destination{0}".format(n))

    class Meta:
        """Factory Configuration."""

        model = Destination


class SourceFactory(BaseFactory):
    """Source factory."""

    plugin_name = "test-source"
    label = Sequence(lambda n: "source{0}".format(n))

    class Meta:
        """Factory Configuration."""

        model = Source


class NotificationFactory(BaseFactory):
    """Notification factory."""

    plugin_name = "test-notification"
    label = Sequence(lambda n: "notification{0}".format(n))

    class Meta:
        """Factory Configuration."""

        model = Notification


class RoleFactory(BaseFactory):
    """Role factory."""

    name = Sequence(lambda n: "role{0}".format(n))

    class Meta:
        """Factory Configuration."""

        model = Role

    @post_generation
    def users(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            for user in extracted:
                self.users.append(user)


class UserFactory(BaseFactory):
    """User Factory."""

    username = Sequence(lambda n: "user{0}".format(n))
    email = Sequence(lambda n: "user{0}@example.com".format(n))
    active = True
    password = FuzzyText(length=24)
    certificates = []

    class Meta:
        """Factory Configuration."""

        model = User

    @post_generation
    def roles(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            for role in extracted:
                self.roles.append(role)

    @post_generation
    def certificates(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            for cert in extracted:
                self.certificates.append(cert)

    @post_generation
    def authorities(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            for authority in extracted:
                self.authorities.append(authority)


class PolicyFactory(BaseFactory):
    """Policy Factory."""

    name = Sequence(lambda n: "endpoint{0}".format(n))

    class Meta:
        """Factory Configuration."""

        model = Policy


class EndpointFactory(BaseFactory):
    """Endpoint Factory."""

    owner = "joe@example.com"
    name = Sequence(lambda n: "endpoint{0}".format(n))
    type = FuzzyChoice(["elb"])
    active = True
    port = FuzzyInteger(0, high=65535)
    dnsname = "endpoint.example.com"
    policy = SubFactory(PolicyFactory)
    certificate = SubFactory(CertificateFactory)
    source = SubFactory(SourceFactory)

    class Meta:
        """Factory Configuration."""

        model = Endpoint


class ApiKeyFactory(BaseFactory):
    """Api Key Factory."""

    name = Sequence(lambda n: "api_key_{0}".format(n))
    revoked = False
    ttl = -1
    issued_at = 1

    class Meta:
        """Factory Configuration."""

        model = ApiKey

    @post_generation
    def user(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            self.userId = extracted.id


class PendingCertificateFactory(BaseFactory):
    """PendingCertificate factory."""

    name = Sequence(lambda n: "pending_certificate{0}".format(n))
    external_id = 12345
    csr = CSR_STR
    chain = INTERMEDIATE_CERT_STR
    private_key = WILDCARD_CERT_KEY
    owner = "joe@example.com"
    status = FuzzyChoice(["valid", "revoked", "unknown"])
    deleted = False
    description = FuzzyText(length=128)
    date_created = FuzzyDate(date(2016, 1, 1), date(2020, 1, 1))
    number_attempts = 0
    rename = False

    class Meta:
        """Factory Configuration."""

        model = PendingCertificate

    @post_generation
    def user(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            self.user_id = extracted.id

    @post_generation
    def authority(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            self.authority_id = extracted.id

    @post_generation
    def notifications(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            for notification in extracted:
                self.notifications.append(notification)

    @post_generation
    def destinations(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            for destination in extracted:
                self.destintations.append(destination)

    @post_generation
    def replaces(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            for replace in extracted:
                self.replaces.append(replace)

    @post_generation
    def sources(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            for source in extracted:
                self.sources.append(source)

    @post_generation
    def domains(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            for domain in extracted:
                self.domains.append(domain)

    @post_generation
    def roles(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted:
            for domain in extracted:
                self.roles.append(domain)


class DnsProviderFactory(BaseFactory):
    """DnsProvider Factory."""

    name = Sequence(lambda n: f"dnsProvider{n}")
    description = FuzzyText(length=128)
    provider_type = FuzzyText(length=128)
    credentials = json.dumps({"account_id": f"{FuzzyInteger(100000, 999999).fuzz()}"})

    class Meta:
        """Factory Configuration."""

        model = DnsProvider
