"""
DNS checking functionality for mail domains.
"""

import re
from typing import Dict, List

import dns.resolver

from core.models import MailDomain


def normalize_txt_value(value: str) -> str:
    """
    Normalize a TXT record value.
    """
    return re.sub(r"\;$", "", re.sub(r"\s*\;\s*", ";", value.strip('"')))


def check_single_record(
    maildomain: MailDomain, expected_record: Dict[str, any]
) -> Dict[str, any]:
    """
    Check a single DNS record for a mail domain.

    Args:
        maildomain: The MailDomain instance
        expected_record: The expected record to check

    Returns:
        Check result dictionary with status and details
    """
    record_type = expected_record["type"]
    target = expected_record["target"]
    expected_value = expected_record["value"]

    # Build the query name
    if target:
        query_name = f"{target}.{maildomain.name}"
    else:
        query_name = maildomain.name

    try:
        # Query DNS records
        if record_type.upper() == "MX":
            answers = dns.resolver.resolve(query_name, "MX")
            found_values = [
                f"{answer.preference} {answer.exchange}" for answer in answers
            ]
        elif record_type.upper() == "TXT":
            answers = dns.resolver.resolve(query_name, "TXT")
            if target.endswith("._domainkey"):
                found_values = [
                    normalize_txt_value(answer.to_text().strip('"').replace('" "', ""))
                    for answer in answers
                ]
            else:
                found_values = [
                    normalize_txt_value(answer.to_text()) for answer in answers
                ]
            expected_value = normalize_txt_value(expected_value)
        else:
            # For other record types, try to resolve them as-is
            answers = dns.resolver.resolve(query_name, record_type)
            found_values = [answer.to_text() for answer in answers]

        # For SPF records, check for duplicates (multiple "v=spf1" TXT records
        # is invalid per RFC 7208 and causes delivery issues)
        if record_type.upper() == "TXT" and expected_value.startswith("v=spf1"):
            spf_records = [v for v in found_values if v.startswith("v=spf1")]
            if len(spf_records) > 1:
                return {"status": "duplicate", "found": found_values}
            # Check for insecure SPF: if we expect -all but found +all or ?all
            if expected_value.endswith("-all"):
                for spf in spf_records:
                    if spf.endswith("+all") or spf.endswith("?all"):
                        return {"status": "insecure", "found": found_values}

        # For DMARC records, check for duplicates and insecure policy
        if record_type.upper() == "TXT" and expected_value.startswith("v=DMARC1"):
            dmarc_records = [v for v in found_values if v.startswith("v=DMARC1")]
            if len(dmarc_records) > 1:
                return {"status": "duplicate", "found": found_values}
            # Check for insecure DMARC: if we expect p=reject or p=quarantine
            # but found p=none
            if "p=none" not in expected_value:
                for dmarc in dmarc_records:
                    if "p=none" in dmarc:
                        return {"status": "insecure", "found": found_values}

        # Check if expected value is in found values
        if expected_value in found_values:
            return {"status": "correct", "found": found_values}

        # Accept ~all as correct when -all is expected for SPF records
        if record_type.upper() == "TXT" and expected_value.endswith("-all"):
            softfail_variant = expected_value[:-4] + "~all"
            if softfail_variant in found_values:
                return {"status": "correct", "found": found_values}

        return {"status": "incorrect", "found": found_values}

    except dns.resolver.NXDOMAIN:
        # Domain doesn't exist
        return {"status": "missing", "error": "Domain not found"}
    except dns.resolver.NoAnswer:
        # No records found for this query
        return {"status": "missing", "error": "No records found"}
    except dns.resolver.NoNameservers:
        # No nameservers found
        return {"status": "missing", "error": "No nameservers found"}
    except dns.resolver.Timeout:
        # DNS query timed out
        return {"status": "error", "error": "DNS query timeout"}
    except dns.resolver.YXDOMAIN:
        # Domain name is too long
        return {"status": "error", "error": "Domain name too long"}
    except Exception as e:  # pylint: disable=broad-exception-caught
        # Other DNS errors
        return {"status": "error", "error": f"DNS query failed: {str(e)}"}


def check_dns_records(maildomain: MailDomain) -> List[Dict[str, any]]:
    """
    Check DNS records for a mail domain against expected records.

    Args:
        maildomain: The MailDomain instance to check

    Returns:
        List of records with their check status
    """
    expected_records = maildomain.get_expected_dns_records()
    results = []

    # Collect expected MX values for conflicting detection
    expected_mx_values = {
        record["value"] for record in expected_records if record["type"].upper() == "MX"
    }

    for expected_record in expected_records:
        result_record = expected_record.copy()
        result_record["_check"] = check_single_record(maildomain, expected_record)

        # For MX records that are correct, check for extra (conflicting) MX entries
        if (
            expected_record["type"].upper() == "MX"
            and result_record["_check"]["status"] == "correct"
        ):
            found = result_record["_check"].get("found", [])
            extra_mx = [v for v in found if v not in expected_mx_values]
            if extra_mx:
                result_record["_check"]["status"] = "conflicting"

        results.append(result_record)

    return results
