from __future__ import annotations
from typing import List, Tuple

from models import SyntaxInfo, MxInfo, DmarcInfo, SpfInfo, DkimInfo


def clamp(x: int, lo: int, hi: int) -> int:
    return max(lo, min(hi, x))


def score(syn: SyntaxInfo, mx: MxInfo, dmarc: DmarcInfo, spf: SpfInfo, dkim: DkimInfo) -> Tuple[int, List[str]]:
    s = 0
    reasons: List[str] = []

    # Syntax
    if syn.idn:
        s += 20
        reasons.append("idn_or_punycode")
    if syn.mixed_scripts:
        s += 20
        reasons.append("mixed_scripts")
    if syn.suspicious_tokens:
        s += 10
        reasons.append("suspicious_tokens")
    if syn.brand_in_subdomain:
        s += 20
        reasons.append("brand_in_subdomain")

    if syn.typo_matches:
        best = syn.typo_matches[0]["distance"]
        if best == 1:
            s += 30
            reasons.append("typosquat_distance_1")
        elif best == 2:
            s += 15
            reasons.append("typosquat_distance_2")

    # DNS posture
    if not mx.present:
        s += 5
        reasons.append("no_mx")

    if not dmarc.present:
        s += 15
        reasons.append("no_dmarc")
    else:
        if dmarc.ambiguous:
            s += 10
            reasons.append("ambiguous_dmarc_records")
        if dmarc.policy == "none":
            s += 10
            reasons.append("dmarc_policy_none")
        if dmarc.pct is not None and dmarc.pct < 100:
            s += 5
            reasons.append("dmarc_pct_below_100")
        if dmarc.rua_present is False:
            s += 5
            reasons.append("no_dmarc_reporting_rua")

    # SPF
    if not spf.present:
        s += 10
        reasons.append("no_spf")
    else:
        if spf.multiple:
            s += 15
            reasons.append("multiple_spf_records")

        dmarc_is_weak = (not dmarc.present) or (dmarc.policy == "none")

        if spf.weak_all:
            if dmarc_is_weak:
                s += 10
                reasons.append("spf_weak_all_under_weak_dmarc")
            else:
                s += 5
                reasons.append("spf_weak_all")



    if dkim.probed and not dkim.found_selectors:
        s += 5
        reasons.append("dkim_not_found_common_selectors")

    return clamp(s, 0, 100), reasons


def level(score_val: int) -> str:
    if score_val >= 60:
        return "HIGH"
    if score_val >= 30:
        return "MEDIUM"
    return "LOW"
