import inject
from acmev2.handlers import handle, ChallengeRequestHandler
from acmev2.challenges import validator_for
from acmev2.models import (
    OrderResource,
    AccountResource,
    ChallengeStatus,
    AuthorizationStatus,
)
from acmev2.models.challenge import ChallengeType
from acmev2.settings import ACMESettings, Challenges
from acmev2.tests.conftest import MessageBuilder, default_request_headers
from acmev2.tests.helpers import authz_by_url, select_first
from acmev2.services import IChallengeService
from acme.challenges import DNS01
from box import Box
import pytest
from xprocess import ProcessStarter
import tempfile
import os
import josepy
from ..dns.server import DNSServer, Zone


class TestDnsChallenges:

    dns_server_port = 11345

    @pytest.fixture(autouse=True)
    def setup(self, setup):
        settings = inject.instance(ACMESettings)
        settings.challenges_available = [Challenges.http_01, Challenges.dns_01]
        settings.dns_challenge_nameservers = [f"127.0.0.1:{self.dns_server_port}"]

    @pytest.fixture(scope="function")
    def dns_server(self):
        dns_server = DNSServer(port=self.dns_server_port)
        dns_server.start()

        yield dns_server

        dns_server.stop()

    @pytest.mark.slow
    def test_dns_01_chall(
        self,
        default_order: OrderResource,
        default_account: AccountResource,
        message_builder: MessageBuilder,
        dns_server: DNSServer,
    ):
        authz_resource = default_order.authorizations[0]
        dns_chall = select_first(
            authz_resource.challenges, lambda chall: chall.type == ChallengeType.dns_01
        )

        assert dns_chall is not None
        chall_request = message_builder(acct_id=default_account.id, url=dns_chall.url)

        response = handle(
            ChallengeRequestHandler(
                dns_chall.url,
                "POST",
                msg=chall_request,
                headers=default_request_headers(),
            )
        )
        resource = Box(response.msg.model_dump())

        assert resource.status == "processing"
        assert resource.type == "dns-01"

        chall_service = inject.instance(IChallengeService)
        with pytest.raises(Exception):
            dns_chall = chall_service.validate(
                default_account.jwk, default_order, authz_resource, dns_chall
            )

        # Initial validation should not work, DNS fails
        assert dns_chall.status != ChallengeStatus.valid
        chall_validator = DNS01(token=josepy.decode_b64jose(dns_chall.token))

        dns_server.add_record(
            Zone(
                f"_acme-challenge.{authz_resource.identifier.value}",
                "TXT",
                chall_validator.validation(default_account.jwk),
            )
        )

        dns_chall = chall_service.validate(
            default_account.jwk, default_order, authz_resource, dns_chall
        )

        assert dns_chall.status == ChallengeStatus.valid
        assert dns_chall.validated is not None
        assert authz_resource.status == AuthorizationStatus.valid

    @pytest.mark.slow
    def test_dns_01_chall_invalid_content(
        self,
        default_order: OrderResource,
        default_account: AccountResource,
        message_builder: MessageBuilder,
        dns_server: DNSServer,
    ):
        authz_resource = default_order.authorizations[0]
        dns_chall = select_first(
            authz_resource.challenges, lambda chall: chall.type == ChallengeType.dns_01
        )

        assert dns_chall is not None
        chall_request = message_builder(acct_id=default_account.id, url=dns_chall.url)

        response = handle(
            ChallengeRequestHandler(
                dns_chall.url,
                "POST",
                msg=chall_request,
                headers=default_request_headers(),
            )
        )
        resource = Box(response.msg.model_dump())

        assert resource.status == "processing"
        assert resource.type == "dns-01"

        dns_server.add_record(
            Zone(
                f"_acme-challenge.{authz_resource.identifier.value}",
                "TXT",
                "invalid-token",
            )
        )

        chall_service = inject.instance(IChallengeService)
        with pytest.raises(Exception):
            dns_chall = chall_service.validate(
                default_account.jwk, default_order, authz_resource, dns_chall
            )

        # Validation should not work, DNS fails
        assert dns_chall.status != ChallengeStatus.valid

    @pytest.mark.slow
    def test_dns_01_server_down(
        self,
        default_order: OrderResource,
        default_account: AccountResource,
        message_builder: MessageBuilder,
    ):
        authz_resource = default_order.authorizations[0]
        dns_chall = select_first(
            authz_resource.challenges, lambda chall: chall.type == ChallengeType.dns_01
        )

        assert dns_chall is not None
        chall_request = message_builder(acct_id=default_account.id, url=dns_chall.url)

        response = handle(
            ChallengeRequestHandler(
                dns_chall.url,
                "POST",
                msg=chall_request,
                headers=default_request_headers(),
            )
        )
        resource = Box(response.msg.model_dump())

        assert resource.status == "processing"
        assert resource.type == "dns-01"

        chall_service = inject.instance(IChallengeService)
        with pytest.raises(Exception):
            dns_chall = chall_service.validate(
                default_account.jwk, default_order, authz_resource, dns_chall
            )

        # Validation should not work, DNS fails
        assert dns_chall.status != ChallengeStatus.valid

    @pytest.mark.slow
    def test_dns_01_cname(
        self,
        default_order: OrderResource,
        default_account: AccountResource,
        message_builder: MessageBuilder,
        dns_server: DNSServer,
    ):
        authz_resource = default_order.authorizations[0]
        dns_chall = select_first(
            authz_resource.challenges, lambda chall: chall.type == ChallengeType.dns_01
        )

        assert dns_chall is not None
        chall_request = message_builder(acct_id=default_account.id, url=dns_chall.url)

        response = handle(
            ChallengeRequestHandler(
                dns_chall.url,
                "POST",
                msg=chall_request,
                headers=default_request_headers(),
            )
        )
        resource = Box(response.msg.model_dump())

        assert resource.status == "processing"
        assert resource.type == "dns-01"

        dns_server.add_record(
            Zone(
                f"_acme-challenge.{authz_resource.identifier.value}",
                "CNAME",
                "delegated-domain.localhost",
            )
        )

        chall_validator = DNS01(token=josepy.decode_b64jose(dns_chall.token))

        dns_server.add_record(
            Zone(
                "delegated-domain.localhost",
                "TXT",
                chall_validator.validation(default_account.jwk),
            )
        )

        chall_service = inject.instance(IChallengeService)
        dns_chall = chall_service.validate(
            default_account.jwk, default_order, authz_resource, dns_chall
        )

        assert dns_chall.status == ChallengeStatus.valid
