"""
Competitive Monitor - Track SEO Competitive Changes Over Time
=============================================================
Purpose: Monitor traffic trends, DR changes, keyword movement,
         content velocity, and generate competitive alerts.
Python: 3.10+

Usage:
    python competitive_monitor.py --target https://example.com --period 30 --json
    python competitive_monitor.py --target https://example.com --competitor https://comp1.com --period 60 --json
    python competitive_monitor.py --target https://example.com --scope traffic --period 90 --json
"""

import argparse
import asyncio
import json
import logging
import sys
from dataclasses import dataclass, field, asdict
from datetime import datetime, timedelta
from typing import Any
from urllib.parse import urlparse

from base_client import BaseAsyncClient, config

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# Data classes
# ---------------------------------------------------------------------------

@dataclass
class TrafficTrend:
    """Traffic trend for a domain over a time period."""
    domain: str = ""
    data_points: list[dict[str, Any]] = field(default_factory=list)
    direction: str = "stable"  # up / down / stable
    growth_rate: float = 0.0
    current_traffic: int = 0
    period_start_traffic: int = 0


@dataclass
class DrTrend:
    """Domain Rating trend for a domain."""
    domain: str = ""
    data_points: list[dict[str, Any]] = field(default_factory=list)
    direction: str = "stable"
    current_dr: float = 0.0
    period_start_dr: float = 0.0
    change: float = 0.0


@dataclass
class KeywordMovement:
    """Keyword gains and losses for a domain."""
    domain: str = ""
    new_keywords: int = 0
    lost_keywords: int = 0
    net_change: int = 0
    improved_positions: int = 0
    declined_positions: int = 0


@dataclass
class ContentVelocity:
    """Content publication rate metrics."""
    domain: str = ""
    new_pages_per_month: float = 0.0
    total_pages_start: int = 0
    total_pages_end: int = 0
    net_new_pages: int = 0
    avg_word_count: int = 0


@dataclass
class CompetitiveAlert:
    """Alert for significant competitive movement."""
    domain: str = ""
    alert_type: str = ""  # traffic_surge, dr_jump, keyword_surge, content_burst
    message: str = ""
    severity: str = "info"  # info / warning / critical
    metric_value: float = 0.0
    threshold: float = 0.0


@dataclass
class MarketShare:
    """Market share estimation for a domain within its competitive set."""
    domain: str = ""
    traffic_share_pct: float = 0.0
    keyword_share_pct: float = 0.0
    overall_share_pct: float = 0.0


@dataclass
class CompetitiveMonitorResult:
    """Full monitoring result."""
    target: str = ""
    period_days: int = 30
    scope: str = "all"
    traffic_trends: list[TrafficTrend] = field(default_factory=list)
    dr_trends: list[DrTrend] = field(default_factory=list)
    keyword_movements: list[KeywordMovement] = field(default_factory=list)
    content_velocities: list[ContentVelocity] = field(default_factory=list)
    alerts: list[CompetitiveAlert] = field(default_factory=list)
    market_shares: list[MarketShare] = field(default_factory=list)
    timestamp: str = ""
    errors: list[str] = field(default_factory=list)


# ---------------------------------------------------------------------------
# Monitor
# ---------------------------------------------------------------------------

class CompetitiveMonitor(BaseAsyncClient):
    """Monitor competitive landscape changes using Ahrefs MCP tools."""

    # Alert thresholds
    TRAFFIC_CHANGE_THRESHOLD = 0.20   # 20% change triggers alert
    DR_CHANGE_THRESHOLD = 3.0         # 3-point DR change
    KEYWORD_SURGE_THRESHOLD = 0.15    # 15% keyword growth
    CONTENT_BURST_THRESHOLD = 2.0     # 2x normal content velocity

    def __init__(self):
        super().__init__(max_concurrent=5, requests_per_second=2.0)

    @staticmethod
    def _extract_domain(url: str) -> str:
        """Extract bare domain from URL or return as-is if already bare."""
        if "://" in url:
            parsed = urlparse(url)
            return parsed.netloc.lower().replace("www.", "")
        return url.lower().replace("www.", "")

    async def _call_ahrefs(self, tool: str, params: dict[str, Any]) -> dict:
        """Simulate Ahrefs MCP call. In production, routed via MCP bridge."""
        self.logger.info(f"Ahrefs MCP call: {tool} | params={params}")
        return {"tool": tool, "params": params, "data": {}}

    async def _discover_competitors(
        self, target: str, limit: int = 10
    ) -> list[str]:
        """Discover competitors if none specified."""
        domain = self._extract_domain(target)
        resp = await self._call_ahrefs(
            "site-explorer-organic-competitors",
            {"target": domain, "limit": limit},
        )
        competitors_raw = resp.get("data", {}).get("competitors", [])
        return [
            c.get("domain", "")
            for c in competitors_raw
            if c.get("domain") and c.get("domain") != domain
        ][:limit]

    async def get_traffic_trends(
        self, domains: list[str], period: int
    ) -> list[TrafficTrend]:
        """Get traffic trend data for multiple domains."""
        trends: list[TrafficTrend] = []
        date_from = (datetime.now() - timedelta(days=period)).strftime("%Y-%m-%d")

        for domain in domains:
            domain = self._extract_domain(domain)
            trend = TrafficTrend(domain=domain)

            resp = await self._call_ahrefs(
                "site-explorer-metrics-history",
                {"target": domain, "date_from": date_from},
            )
            data_points = resp.get("data", {}).get("data_points", [])
            trend.data_points = data_points

            if len(data_points) >= 2:
                first_traffic = int(data_points[0].get("organic_traffic", 0))
                last_traffic = int(data_points[-1].get("organic_traffic", 0))
                trend.period_start_traffic = first_traffic
                trend.current_traffic = last_traffic

                if first_traffic > 0:
                    growth = ((last_traffic - first_traffic) / first_traffic) * 100
                    trend.growth_rate = round(growth, 2)

                    if growth > 5:
                        trend.direction = "up"
                    elif growth < -5:
                        trend.direction = "down"
                    else:
                        trend.direction = "stable"
                else:
                    trend.direction = "new" if last_traffic > 0 else "stable"

            trends.append(trend)
            self.logger.info(
                f"Traffic trend for {domain}: {trend.direction} "
                f"({trend.growth_rate:+.1f}%)"
            )

        return trends

    async def get_dr_trends(
        self, domains: list[str], period: int
    ) -> list[DrTrend]:
        """Get Domain Rating trend data for multiple domains."""
        trends: list[DrTrend] = []
        date_from = (datetime.now() - timedelta(days=period)).strftime("%Y-%m-%d")

        for domain in domains:
            domain = self._extract_domain(domain)
            trend = DrTrend(domain=domain)

            resp = await self._call_ahrefs(
                "site-explorer-domain-rating-history",
                {"target": domain, "date_from": date_from},
            )
            data_points = resp.get("data", {}).get("data_points", [])
            trend.data_points = data_points

            if len(data_points) >= 2:
                first_dr = float(data_points[0].get("domain_rating", 0))
                last_dr = float(data_points[-1].get("domain_rating", 0))
                trend.period_start_dr = first_dr
                trend.current_dr = last_dr
                trend.change = round(last_dr - first_dr, 1)

                if trend.change > 1:
                    trend.direction = "up"
                elif trend.change < -1:
                    trend.direction = "down"
                else:
                    trend.direction = "stable"

            trends.append(trend)
            self.logger.info(
                f"DR trend for {domain}: {trend.direction} "
                f"(change={trend.change:+.1f})"
            )

        return trends

    async def track_keyword_movement(
        self, domains: list[str], period: int
    ) -> list[KeywordMovement]:
        """Track new/lost keywords for each domain over the period."""
        movements: list[KeywordMovement] = []

        for domain in domains:
            domain = self._extract_domain(domain)
            movement = KeywordMovement(domain=domain)

            # Current keyword count
            current_resp = await self._call_ahrefs(
                "site-explorer-metrics",
                {"target": domain},
            )
            current_kw = int(
                current_resp.get("data", {}).get("organic_keywords", 0)
            )

            # Historical keyword count
            date_from = (datetime.now() - timedelta(days=period)).strftime("%Y-%m-%d")
            hist_resp = await self._call_ahrefs(
                "site-explorer-metrics-history",
                {"target": domain, "date_from": date_from},
            )
            data_points = hist_resp.get("data", {}).get("data_points", [])

            if data_points:
                start_kw = int(data_points[0].get("organic_keywords", 0))
            else:
                start_kw = current_kw

            net_change = current_kw - start_kw
            movement.net_change = net_change

            # Estimate new vs lost (simplified: positive net = new > lost)
            if net_change > 0:
                movement.new_keywords = net_change
                movement.lost_keywords = 0
                movement.improved_positions = int(net_change * 0.6)
                movement.declined_positions = int(net_change * 0.1)
            elif net_change < 0:
                movement.new_keywords = 0
                movement.lost_keywords = abs(net_change)
                movement.improved_positions = 0
                movement.declined_positions = abs(net_change)
            else:
                movement.new_keywords = 0
                movement.lost_keywords = 0

            movements.append(movement)
            self.logger.info(
                f"Keyword movement for {domain}: net={movement.net_change:+d}"
            )

        return movements

    async def compare_content_velocity(
        self, domains: list[str], period: int
    ) -> list[ContentVelocity]:
        """Compare content publication velocity across domains."""
        velocities: list[ContentVelocity] = []
        date_from = (datetime.now() - timedelta(days=period)).strftime("%Y-%m-%d")

        for domain in domains:
            domain = self._extract_domain(domain)
            velocity = ContentVelocity(domain=domain)

            resp = await self._call_ahrefs(
                "site-explorer-pages-history",
                {"target": domain, "date_from": date_from},
            )
            data_points = resp.get("data", {}).get("data_points", [])

            if len(data_points) >= 2:
                start_pages = int(data_points[0].get("pages", 0))
                end_pages = int(data_points[-1].get("pages", 0))
                velocity.total_pages_start = start_pages
                velocity.total_pages_end = end_pages
                velocity.net_new_pages = end_pages - start_pages

                months = max(period / 30.0, 1.0)
                velocity.new_pages_per_month = round(
                    velocity.net_new_pages / months, 1
                )
            else:
                # Fallback: get current pages count
                metrics_resp = await self._call_ahrefs(
                    "site-explorer-metrics", {"target": domain}
                )
                velocity.total_pages_end = int(
                    metrics_resp.get("data", {}).get("pages", 0)
                )

            velocities.append(velocity)
            self.logger.info(
                f"Content velocity for {domain}: "
                f"{velocity.new_pages_per_month:.1f} pages/month"
            )

        return velocities

    def generate_alerts(
        self,
        traffic_trends: list[TrafficTrend],
        dr_trends: list[DrTrend],
        keyword_movements: list[KeywordMovement],
        content_velocities: list[ContentVelocity],
        target_domain: str,
    ) -> list[CompetitiveAlert]:
        """Generate alerts for significant competitive movements."""
        alerts: list[CompetitiveAlert] = []
        target_domain = self._extract_domain(target_domain)

        for trend in traffic_trends:
            if trend.domain == target_domain:
                continue
            abs_growth = abs(trend.growth_rate) / 100.0
            if abs_growth >= self.TRAFFIC_CHANGE_THRESHOLD:
                severity = "critical" if abs_growth >= 0.50 else "warning"
                direction = "surge" if trend.growth_rate > 0 else "decline"
                alerts.append(CompetitiveAlert(
                    domain=trend.domain,
                    alert_type=f"traffic_{direction}",
                    message=(
                        f"{trend.domain} traffic {direction}: "
                        f"{trend.growth_rate:+.1f}% "
                        f"({trend.period_start_traffic:,} -> {trend.current_traffic:,})"
                    ),
                    severity=severity,
                    metric_value=trend.growth_rate,
                    threshold=self.TRAFFIC_CHANGE_THRESHOLD * 100,
                ))

        for trend in dr_trends:
            if trend.domain == target_domain:
                continue
            if abs(trend.change) >= self.DR_CHANGE_THRESHOLD:
                severity = "warning" if abs(trend.change) < 5 else "critical"
                direction = "jump" if trend.change > 0 else "drop"
                alerts.append(CompetitiveAlert(
                    domain=trend.domain,
                    alert_type=f"dr_{direction}",
                    message=(
                        f"{trend.domain} DR {direction}: "
                        f"{trend.change:+.1f} points "
                        f"({trend.period_start_dr:.1f} -> {trend.current_dr:.1f})"
                    ),
                    severity=severity,
                    metric_value=trend.change,
                    threshold=self.DR_CHANGE_THRESHOLD,
                ))

        for movement in keyword_movements:
            if movement.domain == target_domain:
                continue
            # Check for keyword surge relative to total keywords
            if movement.new_keywords > 0:
                total_est = max(movement.new_keywords + movement.lost_keywords, 1)
                surge_ratio = movement.new_keywords / total_est
                if surge_ratio >= self.KEYWORD_SURGE_THRESHOLD:
                    alerts.append(CompetitiveAlert(
                        domain=movement.domain,
                        alert_type="keyword_surge",
                        message=(
                            f"{movement.domain} gained {movement.new_keywords} "
                            f"new keywords (net: {movement.net_change:+d})"
                        ),
                        severity="warning",
                        metric_value=float(movement.new_keywords),
                        threshold=self.KEYWORD_SURGE_THRESHOLD * 100,
                    ))

        # Check for content burst
        if content_velocities:
            avg_velocity = (
                sum(v.new_pages_per_month for v in content_velocities)
                / len(content_velocities)
            )
            for vel in content_velocities:
                if vel.domain == target_domain:
                    continue
                if avg_velocity > 0 and vel.new_pages_per_month > avg_velocity * self.CONTENT_BURST_THRESHOLD:
                    alerts.append(CompetitiveAlert(
                        domain=vel.domain,
                        alert_type="content_burst",
                        message=(
                            f"{vel.domain} publishing {vel.new_pages_per_month:.0f} "
                            f"pages/month ({self.CONTENT_BURST_THRESHOLD:.0f}x above average)"
                        ),
                        severity="info",
                        metric_value=vel.new_pages_per_month,
                        threshold=avg_velocity * self.CONTENT_BURST_THRESHOLD,
                    ))

        # Sort alerts by severity
        severity_order = {"critical": 0, "warning": 1, "info": 2}
        alerts.sort(key=lambda a: severity_order.get(a.severity, 3))

        self.logger.info(f"Generated {len(alerts)} competitive alerts")
        return alerts

    def estimate_market_share(
        self,
        traffic_trends: list[TrafficTrend],
        keyword_movements: list[KeywordMovement],
    ) -> list[MarketShare]:
        """Estimate market share based on organic traffic within competitive set."""
        shares: list[MarketShare] = []

        total_traffic = sum(t.current_traffic for t in traffic_trends) or 1
        total_kw_est = sum(
            max(m.new_keywords + abs(m.net_change), 1) for m in keyword_movements
        ) or 1

        kw_by_domain: dict[str, int] = {}
        for m in keyword_movements:
            kw_by_domain[m.domain] = max(m.new_keywords + abs(m.net_change), 1)

        for trend in traffic_trends:
            share = MarketShare(domain=trend.domain)
            share.traffic_share_pct = round(
                (trend.current_traffic / total_traffic) * 100, 2
            )
            kw_count = kw_by_domain.get(trend.domain, 1)
            share.keyword_share_pct = round(
                (kw_count / total_kw_est) * 100, 2
            )
            share.overall_share_pct = round(
                share.traffic_share_pct * 0.7 + share.keyword_share_pct * 0.3, 2
            )
            shares.append(share)

        shares.sort(key=lambda s: s.overall_share_pct, reverse=True)
        return shares

    async def monitor(
        self,
        target: str,
        competitors: list[str] | None = None,
        period: int = 30,
        scope: str = "all",
    ) -> CompetitiveMonitorResult:
        """Orchestrate full competitive monitoring pipeline."""
        timestamp = datetime.now().isoformat()
        target_domain = self._extract_domain(target)
        result = CompetitiveMonitorResult(
            target=target_domain,
            period_days=period,
            scope=scope,
            timestamp=timestamp,
        )

        try:
            # Discover competitors if not provided
            if competitors:
                comp_domains = [self._extract_domain(c) for c in competitors]
            else:
                self.logger.info("Auto-discovering competitors...")
                comp_domains = await self._discover_competitors(target, limit=10)

            all_domains = [target_domain] + comp_domains
            self.logger.info(
                f"Monitoring {len(all_domains)} domains over {period} days"
            )

            # Traffic trends
            if scope in ("all", "traffic"):
                self.logger.info("Fetching traffic trends...")
                result.traffic_trends = await self.get_traffic_trends(
                    all_domains, period
                )

            # DR trends
            if scope in ("all", "traffic"):
                self.logger.info("Fetching DR trends...")
                result.dr_trends = await self.get_dr_trends(all_domains, period)

            # Keyword movements
            if scope in ("all", "keywords"):
                self.logger.info("Tracking keyword movements...")
                result.keyword_movements = await self.track_keyword_movement(
                    all_domains, period
                )

            # Content velocity
            if scope in ("all", "content"):
                self.logger.info("Comparing content velocity...")
                result.content_velocities = await self.compare_content_velocity(
                    all_domains, period
                )

            # Generate alerts
            self.logger.info("Generating competitive alerts...")
            result.alerts = self.generate_alerts(
                traffic_trends=result.traffic_trends,
                dr_trends=result.dr_trends,
                keyword_movements=result.keyword_movements,
                content_velocities=result.content_velocities,
                target_domain=target_domain,
            )

            # Market share estimation
            if result.traffic_trends and result.keyword_movements:
                self.logger.info("Estimating market shares...")
                result.market_shares = self.estimate_market_share(
                    result.traffic_trends,
                    result.keyword_movements,
                )

            self.logger.info(
                f"Monitoring complete: {len(result.alerts)} alerts generated"
            )

        except Exception as e:
            msg = f"Monitoring pipeline error: {e}"
            self.logger.error(msg)
            result.errors.append(msg)

        return result


# ---------------------------------------------------------------------------
# Output helpers
# ---------------------------------------------------------------------------

def _format_text_report(result: CompetitiveMonitorResult) -> str:
    """Format monitoring result as human-readable text report."""
    lines: list[str] = []
    lines.append("=" * 70)
    lines.append("  COMPETITIVE MONITORING REPORT")
    lines.append(f"  Target: {result.target}")
    lines.append(f"  Period: {result.period_days} days | Scope: {result.scope}")
    lines.append(f"  Generated: {result.timestamp}")
    lines.append("=" * 70)

    # Alerts section
    if result.alerts:
        lines.append("")
        lines.append("--- ALERTS ---")
        for alert in result.alerts:
            icon = {"critical": "[!]", "warning": "[*]", "info": "[-]"}.get(
                alert.severity, "[-]"
            )
            lines.append(f"  {icon} [{alert.severity.upper()}] {alert.message}")

    # Traffic trends
    if result.traffic_trends:
        lines.append("")
        lines.append("--- TRAFFIC TRENDS ---")
        lines.append(f"  {'Domain':<30} {'Direction':>10} {'Growth':>10} {'Current':>12}")
        lines.append("  " + "-" * 65)
        for t in result.traffic_trends:
            marker = "*" if t.domain == result.target else " "
            lines.append(
                f"  {marker}{t.domain:<29} {t.direction:>10} "
                f"{t.growth_rate:>+9.1f}% {t.current_traffic:>11,}"
            )

    # DR trends
    if result.dr_trends:
        lines.append("")
        lines.append("--- DOMAIN RATING TRENDS ---")
        lines.append(f"  {'Domain':<30} {'Direction':>10} {'Change':>10} {'Current':>10}")
        lines.append("  " + "-" * 63)
        for d in result.dr_trends:
            marker = "*" if d.domain == result.target else " "
            lines.append(
                f"  {marker}{d.domain:<29} {d.direction:>10} "
                f"{d.change:>+9.1f} {d.current_dr:>9.1f}"
            )

    # Keyword movements
    if result.keyword_movements:
        lines.append("")
        lines.append("--- KEYWORD MOVEMENTS ---")
        lines.append(
            f"  {'Domain':<30} {'New':>8} {'Lost':>8} {'Net':>10}"
        )
        lines.append("  " + "-" * 59)
        for k in result.keyword_movements:
            marker = "*" if k.domain == result.target else " "
            lines.append(
                f"  {marker}{k.domain:<29} {k.new_keywords:>8,} "
                f"{k.lost_keywords:>8,} {k.net_change:>+9,}"
            )

    # Content velocity
    if result.content_velocities:
        lines.append("")
        lines.append("--- CONTENT VELOCITY ---")
        lines.append(
            f"  {'Domain':<30} {'Pages/Mo':>10} {'Net New':>10} {'Total':>10}"
        )
        lines.append("  " + "-" * 63)
        for v in result.content_velocities:
            marker = "*" if v.domain == result.target else " "
            lines.append(
                f"  {marker}{v.domain:<29} {v.new_pages_per_month:>9.1f} "
                f"{v.net_new_pages:>9,} {v.total_pages_end:>9,}"
            )

    # Market share
    if result.market_shares:
        lines.append("")
        lines.append("--- ESTIMATED MARKET SHARE ---")
        lines.append(
            f"  {'Domain':<30} {'Traffic%':>10} {'Keywords%':>10} {'Overall%':>10}"
        )
        lines.append("  " + "-" * 63)
        for s in result.market_shares:
            marker = "*" if s.domain == result.target else " "
            lines.append(
                f"  {marker}{s.domain:<29} {s.traffic_share_pct:>9.1f}% "
                f"{s.keyword_share_pct:>9.1f}% {s.overall_share_pct:>9.1f}%"
            )

    if result.errors:
        lines.append("")
        lines.append("--- ERRORS ---")
        for err in result.errors:
            lines.append(f"  - {err}")

    lines.append("")
    lines.append("  * = target domain")
    lines.append("=" * 70)
    return "\n".join(lines)


def _serialize_result(result: CompetitiveMonitorResult) -> dict:
    """Convert result to JSON-serializable dict."""
    output = {
        "target": result.target,
        "period_days": result.period_days,
        "scope": result.scope,
        "traffic_trends": [asdict(t) for t in result.traffic_trends],
        "dr_trends": [asdict(d) for d in result.dr_trends],
        "keyword_movements": [asdict(k) for k in result.keyword_movements],
        "content_velocities": [asdict(v) for v in result.content_velocities],
        "alerts": [asdict(a) for a in result.alerts],
        "market_shares": [asdict(s) for s in result.market_shares],
        "timestamp": result.timestamp,
    }
    if result.errors:
        output["errors"] = result.errors
    return output


# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------

def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Competitive Monitor - Track SEO competitive changes over time",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""\
Examples:
  python competitive_monitor.py --target https://example.com --period 30 --json
  python competitive_monitor.py --target https://example.com --competitor https://comp1.com --period 60 --json
  python competitive_monitor.py --target https://example.com --scope traffic --period 90 --json
        """,
    )
    parser.add_argument(
        "--target",
        required=True,
        help="Target website URL or domain to monitor",
    )
    parser.add_argument(
        "--competitor",
        action="append",
        dest="competitors",
        default=[],
        help="Competitor URL/domain (repeatable; omit for auto-discovery)",
    )
    parser.add_argument(
        "--period",
        type=int,
        default=30,
        help="Monitoring period in days (default: 30)",
    )
    parser.add_argument(
        "--scope",
        choices=["all", "traffic", "keywords", "content"],
        default="all",
        help="Monitoring scope (default: all)",
    )
    parser.add_argument(
        "--json",
        action="store_true",
        default=False,
        help="Output in JSON format",
    )
    parser.add_argument(
        "--output",
        type=str,
        default=None,
        help="Save output to file path",
    )
    return parser.parse_args(argv)


async def async_main(args: argparse.Namespace) -> None:
    monitor = CompetitiveMonitor()

    result = await monitor.monitor(
        target=args.target,
        competitors=args.competitors or None,
        period=args.period,
        scope=args.scope,
    )

    if args.json:
        output_str = json.dumps(_serialize_result(result), indent=2, ensure_ascii=False)
    else:
        output_str = _format_text_report(result)

    if args.output:
        with open(args.output, "w", encoding="utf-8") as f:
            f.write(output_str)
        logger.info(f"Report saved to {args.output}")
    else:
        print(output_str)

    monitor.print_stats()


def main() -> None:
    args = parse_args()
    asyncio.run(async_main(args))


if __name__ == "__main__":
    main()
