import statistics

from .models import DnsResult, Issue, MetricAlert, ProbeResult, Severity


def _analyze_gateway(gw: ProbeResult) -> tuple[list[Issue], list[MetricAlert]]:
    issues: list[Issue] = []
    alerts: list[MetricAlert] = []
    if gw.loss_pct > 0:
        issues.append(
            Issue(
                Severity.ERROR,
                "gateway-loss",
                source="LAN",
                name="packet loss to gateway",
                cause="wifi/router issue",
            )
        )
        alerts.append(MetricAlert(gw.host, "loss", Severity.ERROR))
    assert gw.avg is not None
    assert gw.jitter is not None
    high_latency = gw.avg > 10
    elevated_latency = gw.avg > 3
    high_jitter = gw.jitter > 5
    if high_latency or high_jitter:
        if high_latency:
            alerts.append(MetricAlert(gw.host, "rtt", Severity.ERROR))
        if high_jitter:
            alerts.append(MetricAlert(gw.host, "jitter", Severity.ERROR))
        issues.append(
            Issue(
                Severity.ERROR,
                "gateway-quality",
                source="LAN",
                name="unstable connection",
                cause="wifi congestion",
            )
        )
    elif elevated_latency:
        issues.append(
            Issue(
                Severity.WARNING,
                "gateway-quality",
                source="LAN",
                name="elevated latency",
                cause="possible wifi congestion",
            )
        )
        alerts.append(MetricAlert(gw.host, "rtt", Severity.WARNING))
    return issues, alerts


def _analyze_internet(
    inet_reachable: list[ProbeResult], gateway_result: ProbeResult | None
) -> tuple[list[Issue], list[MetricAlert]]:
    issues: list[Issue] = []
    alerts: list[MetricAlert] = []
    inet_avg = statistics.mean(r.avg for r in inet_reachable)
    inet_loss = max(r.loss_pct for r in inet_reachable)
    inet_jitter = max(r.jitter for r in inet_reachable)

    if gateway_result is not None and gateway_result.reachable:
        assert gateway_result.avg is not None
        isp_latency = inet_avg - gateway_result.avg
        if isp_latency > 150:
            issues.append(
                Issue(
                    Severity.ERROR,
                    "isp-latency",
                    source="ISP",
                    name="high latency beyond gateway",
                    cause="ISP or routing issue",
                )
            )
            for r in inet_reachable:
                alerts.append(MetricAlert(r.host, "rtt", Severity.ERROR))
        elif isp_latency > 50:
            issues.append(
                Issue(
                    Severity.WARNING,
                    "isp-latency",
                    source="ISP",
                    name="moderate latency beyond gateway",
                    cause="ISP or routing issue",
                )
            )
            for r in inet_reachable:
                alerts.append(MetricAlert(r.host, "rtt", Severity.WARNING))

        if inet_loss > 0:
            if inet_loss > gateway_result.loss_pct:
                issues.append(
                    Issue(
                        Severity.ERROR,
                        "isp-loss",
                        source="ISP",
                        name="packet loss beyond local network",
                        cause="ISP issue",
                    )
                )
            else:
                issues.append(
                    Issue(
                        Severity.ERROR,
                        "local-loss",
                        source="LAN",
                        name="packet loss from local network",
                        cause="wifi/router issue",
                    )
                )
            for r in inet_reachable:
                if r.loss_pct > 0:
                    alerts.append(MetricAlert(r.host, "loss", Severity.ERROR))
    else:
        if inet_avg > 150:
            issues.append(
                Issue(
                    Severity.ERROR,
                    "internet-latency",
                    source="ISP",
                    name="high latency",
                    cause="ISP or routing issue",
                )
            )
            for r in inet_reachable:
                alerts.append(MetricAlert(r.host, "rtt", Severity.ERROR))
        elif inet_avg > 50:
            issues.append(
                Issue(
                    Severity.WARNING,
                    "internet-latency",
                    source="ISP",
                    name="moderate latency",
                    cause="ISP or routing issue",
                )
            )
            for r in inet_reachable:
                alerts.append(MetricAlert(r.host, "rtt", Severity.WARNING))

        if inet_loss > 0:
            issues.append(
                Issue(
                    Severity.ERROR,
                    "isp-loss",
                    source="ISP",
                    name="packet loss beyond local network",
                    cause="ISP issue",
                )
            )
            for r in inet_reachable:
                if r.loss_pct > 0:
                    alerts.append(MetricAlert(r.host, "loss", Severity.ERROR))

    # Only flag internet jitter if it's not explained by local network jitter
    gw_jitter = 0.0
    if gateway_result is not None and gateway_result.reachable:
        assert gateway_result.jitter is not None
        gw_jitter = gateway_result.jitter
    isp_jitter = inet_jitter - gw_jitter
    if isp_jitter > 20:
        issues.append(
            Issue(
                Severity.ERROR,
                "internet-jitter",
                source="ISP",
                name="high jitter",
                cause="unstable ISP connection",
            )
        )
        for r in inet_reachable:
            if r.jitter is not None and r.jitter - gw_jitter > 20:
                alerts.append(MetricAlert(r.host, "jitter", Severity.ERROR))

    return issues, alerts


def _analyze_dns(
    dns_results: list[DnsResult],
) -> tuple[list[Issue], list[MetricAlert]]:
    issues: list[Issue] = []
    alerts: list[MetricAlert] = []

    for r in dns_results:
        if r.cached_ms is None and r.uncached_ms is None:
            issues.append(
                Issue(
                    Severity.ERROR,
                    f"dns-failure-{r.server}",
                    source="DNS",
                    name=f"{r.label} resolver unreachable",
                    cause="resolver down or blocked by network",
                )
            )
            continue

    # Only apply threshold alerts/issues to configured resolvers — public
    # resolvers are shown for comparison, but their absolute times depend on
    # network RTT and aren't actionable.
    configured = [r for r in dns_results if r.is_configured]
    alternatives = [r for r in dns_results if not r.is_configured]

    configured_uncached_flagged = False
    for cr in configured:
        if cr.uncached_ms is not None:
            if cr.uncached_ms > 250:
                alerts.append(MetricAlert(cr.server, "dns-uncached", Severity.ERROR))
            elif cr.uncached_ms > 100:
                alerts.append(MetricAlert(cr.server, "dns-uncached", Severity.WARNING))

        if cr.uncached_ms is not None and cr.uncached_ms > 250:
            configured_uncached_flagged = True
            issues.append(
                Issue(
                    Severity.ERROR,
                    "dns-uncached-slow",
                    source="DNS",
                    name="slow DNS resolution",
                    cause="resolver or upstream DNS issue",
                )
            )
        elif cr.uncached_ms is not None and cr.uncached_ms > 100:
            configured_uncached_flagged = True
            issues.append(
                Issue(
                    Severity.WARNING,
                    "dns-uncached-slow",
                    source="DNS",
                    name="elevated DNS resolution time",
                    cause="resolver or upstream DNS issue",
                )
            )

    # Only suggest switching DNS if configured resolvers aren't already
    # flagged as slow on their own — avoids redundant noise.
    if not configured_uncached_flagged and configured and alternatives:
        configured_uncached = [
            r.uncached_ms for r in configured if r.uncached_ms is not None
        ]
        alt_uncached = [
            r.uncached_ms for r in alternatives if r.uncached_ms is not None
        ]
        if configured_uncached and alt_uncached:
            worst_configured = max(configured_uncached)
            best_alt = min(alt_uncached)
            best_alt_resolver = next(
                r for r in alternatives if r.uncached_ms == best_alt
            )
            if best_alt > 0 and worst_configured > best_alt * 2:
                issues.append(
                    Issue(
                        Severity.WARNING,
                        "dns-slow-configured",
                        source="DNS",
                        name="configured DNS slower than public alternatives",
                        cause=f"consider switching to {best_alt_resolver.label}",
                    )
                )

    return issues, alerts


def analyze(
    gateway_result: ProbeResult | None,
    internet_results: list[ProbeResult],
    dns_results: list[DnsResult],
) -> tuple[list[Issue], list[MetricAlert]]:
    """Analyze probe results and return issues + metric alerts."""
    gw_reachable = gateway_result is not None and gateway_result.reachable
    inet_reachable = [r for r in internet_results if r.reachable]

    if not gw_reachable and not inet_reachable:
        return [
            Issue(
                Severity.ERROR,
                "no-connectivity",
                source="network",
                name="no targets reachable",
                cause="internet may be down",
            )
        ], []

    issues: list[Issue] = []
    alerts: list[MetricAlert] = []

    # Only flag if we actually tried to probe the gateway and it failed
    if gateway_result is not None and not gw_reachable and inet_reachable:
        issues.append(
            Issue(
                Severity.ERROR,
                "gateway-unreachable",
                source="LAN",
                name="gateway unreachable",
                cause="unusual routing setup",
            )
        )

    if gw_reachable:
        assert gateway_result is not None
        gw_issues, gw_alerts = _analyze_gateway(gateway_result)
        issues.extend(gw_issues)
        alerts.extend(gw_alerts)

    if inet_reachable:
        inet_issues, inet_alerts = _analyze_internet(inet_reachable, gateway_result)
        issues.extend(inet_issues)
        alerts.extend(inet_alerts)

    if dns_results:
        dns_issues, dns_alerts = _analyze_dns(dns_results)
        issues.extend(dns_issues)
        alerts.extend(dns_alerts)

    return issues, alerts
