"""
Backlink Monitoring and Management Module

This module provides tools for monitoring backlink profiles, identifying toxic links,
and managing disavow files for SEO purposes.

Requirements: 5.7
"""

import json
from typing import List, Dict, Optional, Tuple
from dataclasses import dataclass, asdict, field
from enum import Enum
from datetime import datetime, timedelta


class BacklinkQuality(Enum):
    """Quality rating for backlinks."""
    EXCELLENT = "excellent"
    GOOD = "good"
    FAIR = "fair"
    POOR = "poor"
    TOXIC = "toxic"


class BacklinkSource(Enum):
    """Source type of backlinks."""
    INDUSTRY_PUBLICATION = "industry_publication"
    BUSINESS_DIRECTORY = "business_directory"
    LOCAL_DIRECTORY = "local_directory"
    GUEST_POST = "guest_post"
    RESOURCE_PAGE = "resource_page"
    COMPETITOR_BACKLINK = "competitor_backlink"
    BROKEN_LINK_REPLACEMENT = "broken_link_replacement"
    SOCIAL_MEDIA = "social_media"
    FORUM = "forum"
    BLOG_COMMENT = "blog_comment"
    PRESS_RELEASE = "press_release"
    UNKNOWN = "unknown"


@dataclass
class Backlink:
    """Represents a single backlink to the website."""
    
    id: str
    source_url: str
    source_domain: str
    target_url: str
    anchor_text: str
    domain_authority: int
    page_authority: int
    spam_score: int
    backlink_type: str  # "dofollow" or "nofollow"
    source_type: BacklinkSource = BacklinkSource.UNKNOWN
    quality_rating: BacklinkQuality = BacklinkQuality.FAIR
    is_toxic: bool = False
    discovered_date: str = ""
    last_checked: str = ""
    status: str = "active"  # "active", "lost", "pending_verification"
    notes: str = ""
    
    def __post_init__(self):
        if not self.discovered_date:
            self.discovered_date = datetime.now().isoformat()
        if not self.last_checked:
            self.last_checked = datetime.now().isoformat()
    
    def to_dict(self) -> Dict:
        """Convert to dictionary for JSON serialization."""
        data = asdict(self)
        data['source_type'] = self.source_type.value
        data['quality_rating'] = self.quality_rating.value
        return data
    
    def calculate_quality_score(self) -> float:
        """
        Calculate overall quality score for the backlink.
        
        Returns:
            Quality score from 0-100
        """
        score = 100.0
        
        # Reduce score based on spam score
        score -= (self.spam_score * 0.5)
        
        # Reduce score if nofollow
        if self.backlink_type == "nofollow":
            score -= 20
        
        # Reduce score based on domain authority
        if self.domain_authority < 20:
            score -= 30
        elif self.domain_authority < 40:
            score -= 15
        
        # Reduce score if toxic
        if self.is_toxic:
            score -= 50
        
        return max(0, min(100, score))
    
    def determine_quality_rating(self) -> BacklinkQuality:
        """
        Determine quality rating based on metrics.
        
        Returns:
            BacklinkQuality rating
        """
        if self.is_toxic or self.spam_score > 50:
            return BacklinkQuality.TOXIC
        
        score = self.calculate_quality_score()
        
        if score >= 80:
            return BacklinkQuality.EXCELLENT
        elif score >= 60:
            return BacklinkQuality.GOOD
        elif score >= 40:
            return BacklinkQuality.FAIR
        else:
            return BacklinkQuality.POOR


@dataclass
class BacklinkProfile:
    """Represents the complete backlink profile for a domain."""
    
    domain: str
    total_backlinks: int = 0
    referring_domains: int = 0
    dofollow_backlinks: int = 0
    nofollow_backlinks: int = 0
    toxic_backlinks: int = 0
    average_domain_authority: float = 0.0
    average_spam_score: float = 0.0
    last_updated: str = ""
    backlinks: List[Backlink] = field(default_factory=list)
    
    def __post_init__(self):
        if not self.last_updated:
            self.last_updated = datetime.now().isoformat()
    
    def to_dict(self) -> Dict:
        """Convert to dictionary for JSON serialization."""
        return {
            "domain": self.domain,
            "total_backlinks": self.total_backlinks,
            "referring_domains": self.referring_domains,
            "dofollow_backlinks": self.dofollow_backlinks,
            "nofollow_backlinks": self.nofollow_backlinks,
            "toxic_backlinks": self.toxic_backlinks,
            "average_domain_authority": self.average_domain_authority,
            "average_spam_score": self.average_spam_score,
            "last_updated": self.last_updated,
            "backlinks": [bl.to_dict() for bl in self.backlinks]
        }


class BacklinkMonitor:
    """Monitors and analyzes backlink profiles."""
    
    def __init__(self, domain: str):
        """
        Initialize the backlink monitor.
        
        Args:
            domain: Domain to monitor
        """
        self.domain = domain
        self.profile = BacklinkProfile(domain=domain)
        self.backlink_history: List[BacklinkProfile] = []
    
    def add_backlink(
        self,
        source_url: str,
        source_domain: str,
        target_url: str,
        anchor_text: str,
        domain_authority: int,
        page_authority: int,
        spam_score: int,
        backlink_type: str = "dofollow",
        source_type: BacklinkSource = BacklinkSource.UNKNOWN
    ) -> Backlink:
        """
        Add a backlink to the profile.
        
        Args:
            source_url: URL of the page linking to us
            source_domain: Domain of the source
            target_url: URL being linked to
            anchor_text: Anchor text of the link
            domain_authority: Domain authority of source
            page_authority: Page authority of source
            spam_score: Spam score of source (0-100)
            backlink_type: "dofollow" or "nofollow"
            source_type: Type of backlink source
        
        Returns:
            Created Backlink object
        """
        backlink_id = f"bl_{len(self.profile.backlinks)}"
        
        backlink = Backlink(
            id=backlink_id,
            source_url=source_url,
            source_domain=source_domain,
            target_url=target_url,
            anchor_text=anchor_text,
            domain_authority=domain_authority,
            page_authority=page_authority,
            spam_score=spam_score,
            backlink_type=backlink_type,
            source_type=source_type
        )
        
        # Determine quality rating
        backlink.quality_rating = backlink.determine_quality_rating()
        backlink.is_toxic = backlink.quality_rating == BacklinkQuality.TOXIC
        
        self.profile.backlinks.append(backlink)
        self._update_profile_stats()
        
        return backlink
    
    def _update_profile_stats(self) -> None:
        """Update profile statistics based on backlinks."""
        if not self.profile.backlinks:
            return
        
        self.profile.total_backlinks = len(self.profile.backlinks)
        
        # Count dofollow/nofollow
        self.profile.dofollow_backlinks = sum(
            1 for bl in self.profile.backlinks if bl.backlink_type == "dofollow"
        )
        self.profile.nofollow_backlinks = sum(
            1 for bl in self.profile.backlinks if bl.backlink_type == "nofollow"
        )
        
        # Count toxic
        self.profile.toxic_backlinks = sum(
            1 for bl in self.profile.backlinks if bl.is_toxic
        )
        
        # Calculate averages
        if self.profile.backlinks:
            self.profile.average_domain_authority = sum(
                bl.domain_authority for bl in self.profile.backlinks
            ) / len(self.profile.backlinks)
            
            self.profile.average_spam_score = sum(
                bl.spam_score for bl in self.profile.backlinks
            ) / len(self.profile.backlinks)
        
        # Count referring domains
        referring_domains = set(bl.source_domain for bl in self.profile.backlinks)
        self.profile.referring_domains = len(referring_domains)
        
        self.profile.last_updated = datetime.now().isoformat()
    
    def get_toxic_backlinks(self) -> List[Backlink]:
        """
        Get all toxic backlinks.
        
        Returns:
            List of toxic backlinks
        """
        return [bl for bl in self.profile.backlinks if bl.is_toxic]
    
    def get_low_quality_backlinks(self, quality_threshold: BacklinkQuality = BacklinkQuality.POOR) -> List[Backlink]:
        """
        Get low-quality backlinks below a certain threshold.
        
        Args:
            quality_threshold: Minimum quality rating to exclude
        
        Returns:
            List of low-quality backlinks
        """
        quality_order = [
            BacklinkQuality.EXCELLENT,
            BacklinkQuality.GOOD,
            BacklinkQuality.FAIR,
            BacklinkQuality.POOR,
            BacklinkQuality.TOXIC
        ]
        threshold_index = quality_order.index(quality_threshold)
        
        return [
            bl for bl in self.profile.backlinks
            if quality_order.index(bl.quality_rating) >= threshold_index
        ]
    
    def get_backlinks_by_source_type(self, source_type: BacklinkSource) -> List[Backlink]:
        """
        Get backlinks by source type.
        
        Args:
            source_type: Type of backlink source
        
        Returns:
            List of backlinks from specified source type
        """
        return [bl for bl in self.profile.backlinks if bl.source_type == source_type]
    
    def get_backlinks_by_domain(self, domain: str) -> List[Backlink]:
        """
        Get all backlinks from a specific domain.
        
        Args:
            domain: Source domain
        
        Returns:
            List of backlinks from specified domain
        """
        return [bl for bl in self.profile.backlinks if bl.source_domain == domain]
    
    def get_high_quality_backlinks(self) -> List[Backlink]:
        """
        Get high-quality backlinks (excellent or good).
        
        Returns:
            List of high-quality backlinks
        """
        return [
            bl for bl in self.profile.backlinks
            if bl.quality_rating in [BacklinkQuality.EXCELLENT, BacklinkQuality.GOOD]
        ]
    
    def get_backlinks_by_domain_authority(self, min_da: int) -> List[Backlink]:
        """
        Get backlinks from domains with minimum domain authority.
        
        Args:
            min_da: Minimum domain authority
        
        Returns:
            List of backlinks from high-authority domains
        """
        return [bl for bl in self.profile.backlinks if bl.domain_authority >= min_da]
    
    def identify_suspicious_patterns(self) -> Dict[str, List[Backlink]]:
        """
        Identify suspicious backlink patterns that may indicate spam.
        
        Returns:
            Dictionary of suspicious patterns and associated backlinks
        """
        suspicious = {}
        
        # Pattern 1: Multiple backlinks from same domain with exact match anchor text
        domain_anchors: Dict[str, List[Backlink]] = {}
        for bl in self.profile.backlinks:
            key = f"{bl.source_domain}:{bl.anchor_text}"
            if key not in domain_anchors:
                domain_anchors[key] = []
            domain_anchors[key].append(bl)
        
        exact_match_spam = [
            bls for bls in domain_anchors.values()
            if len(bls) > 2 and bls[0].anchor_text.lower() == self.domain.lower()
        ]
        if exact_match_spam:
            suspicious["exact_match_spam"] = [bl for bls in exact_match_spam for bl in bls]
        
        # Pattern 2: High spam score backlinks
        high_spam = [bl for bl in self.profile.backlinks if bl.spam_score > 50]
        if high_spam:
            suspicious["high_spam_score"] = high_spam
        
        # Pattern 3: Low domain authority backlinks
        low_da = [bl for bl in self.profile.backlinks if bl.domain_authority < 10]
        if low_da:
            suspicious["low_domain_authority"] = low_da
        
        # Pattern 4: All nofollow backlinks from same domain
        domain_backlinks: Dict[str, List[Backlink]] = {}
        for bl in self.profile.backlinks:
            if bl.source_domain not in domain_backlinks:
                domain_backlinks[bl.source_domain] = []
            domain_backlinks[bl.source_domain].append(bl)
        
        all_nofollow_domains = [
            bls for bls in domain_backlinks.values()
            if len(bls) > 1 and all(bl.backlink_type == "nofollow" for bl in bls)
        ]
        if all_nofollow_domains:
            suspicious["all_nofollow_domains"] = [bl for bls in all_nofollow_domains for bl in bls]
        
        return suspicious
    
    def get_profile_summary(self) -> Dict:
        """
        Get a summary of the backlink profile.
        
        Returns:
            Dictionary with profile summary
        """
        toxic = self.get_toxic_backlinks()
        high_quality = self.get_high_quality_backlinks()
        
        return {
            "domain": self.domain,
            "total_backlinks": self.profile.total_backlinks,
            "referring_domains": self.profile.referring_domains,
            "dofollow_backlinks": self.profile.dofollow_backlinks,
            "nofollow_backlinks": self.profile.nofollow_backlinks,
            "toxic_backlinks": len(toxic),
            "high_quality_backlinks": len(high_quality),
            "average_domain_authority": round(self.profile.average_domain_authority, 2),
            "average_spam_score": round(self.profile.average_spam_score, 2),
            "health_score": self._calculate_health_score(),
            "last_updated": self.profile.last_updated
        }
    
    def _calculate_health_score(self) -> float:
        """
        Calculate overall backlink profile health score (0-100).
        
        Returns:
            Health score
        """
        if not self.profile.backlinks:
            return 0.0
        
        score = 100.0
        
        # Reduce for toxic backlinks
        toxic_ratio = self.profile.toxic_backlinks / self.profile.total_backlinks
        score -= (toxic_ratio * 50)
        
        # Reduce for low average domain authority
        if self.profile.average_domain_authority < 20:
            score -= 20
        elif self.profile.average_domain_authority < 40:
            score -= 10
        
        # Reduce for high average spam score
        if self.profile.average_spam_score > 50:
            score -= 20
        elif self.profile.average_spam_score > 30:
            score -= 10
        
        # Bonus for high-quality backlinks
        high_quality_ratio = len(self.get_high_quality_backlinks()) / self.profile.total_backlinks
        score += (high_quality_ratio * 20)
        
        return max(0, min(100, score))
    
    def export_profile(self, filepath: str) -> None:
        """
        Export backlink profile to JSON file.
        
        Args:
            filepath: Path to export JSON file
        """
        with open(filepath, 'w') as f:
            json.dump(self.profile.to_dict(), f, indent=2)
    
    def export_toxic_backlinks(self, filepath: str) -> None:
        """
        Export toxic backlinks to JSON file.
        
        Args:
            filepath: Path to export JSON file
        """
        toxic = self.get_toxic_backlinks()
        data = [bl.to_dict() for bl in toxic]
        with open(filepath, 'w') as f:
            json.dump(data, f, indent=2)
    
    def save_checkpoint(self) -> None:
        """Save current profile as checkpoint for history tracking."""
        checkpoint = BacklinkProfile(
            domain=self.profile.domain,
            total_backlinks=self.profile.total_backlinks,
            referring_domains=self.profile.referring_domains,
            dofollow_backlinks=self.profile.dofollow_backlinks,
            nofollow_backlinks=self.profile.nofollow_backlinks,
            toxic_backlinks=self.profile.toxic_backlinks,
            average_domain_authority=self.profile.average_domain_authority,
            average_spam_score=self.profile.average_spam_score,
            backlinks=self.profile.backlinks.copy()
        )
        self.backlink_history.append(checkpoint)


class DisavowManager:
    """Manages Google disavow file creation and submission."""
    
    def __init__(self, domain: str):
        """
        Initialize the disavow manager.
        
        Args:
            domain: Domain to manage disavow file for
        """
        self.domain = domain
        self.disavowed_urls: List[str] = []
        self.disavowed_domains: List[str] = []
        self.disavow_history: List[Dict] = []
    
    def add_disavow_url(self, url: str, reason: str = "") -> None:
        """
        Add a URL to the disavow list.
        
        Args:
            url: URL to disavow
            reason: Reason for disavowing
        """
        if url not in self.disavowed_urls:
            self.disavowed_urls.append(url)
            self.disavow_history.append({
                "type": "url",
                "value": url,
                "reason": reason,
                "added_at": datetime.now().isoformat()
            })
    
    def add_disavow_domain(self, domain: str, reason: str = "") -> None:
        """
        Add a domain to the disavow list.
        
        Args:
            domain: Domain to disavow
            reason: Reason for disavowing
        """
        if domain not in self.disavowed_domains:
            self.disavowed_domains.append(domain)
            self.disavow_history.append({
                "type": "domain",
                "value": domain,
                "reason": reason,
                "added_at": datetime.now().isoformat()
            })
    
    def remove_disavow_url(self, url: str) -> bool:
        """
        Remove a URL from the disavow list.
        
        Args:
            url: URL to remove
        
        Returns:
            True if removed, False if not found
        """
        if url in self.disavowed_urls:
            self.disavowed_urls.remove(url)
            return True
        return False
    
    def remove_disavow_domain(self, domain: str) -> bool:
        """
        Remove a domain from the disavow list.
        
        Args:
            domain: Domain to remove
        
        Returns:
            True if removed, False if not found
        """
        if domain in self.disavowed_domains:
            self.disavowed_domains.remove(domain)
            return True
        return False
    
    def create_disavow_file(self) -> str:
        """
        Create a disavow file content in Google's format.
        
        Returns:
            Disavow file content as string
        """
        lines = []
        
        # Add header comment
        lines.append("# Disavow file for " + self.domain)
        lines.append("# Generated on " + datetime.now().isoformat())
        lines.append("# This file contains URLs and domains to disavow")
        lines.append("")
        
        # Add disavowed domains
        if self.disavowed_domains:
            lines.append("# Disavowed Domains")
            for domain in sorted(self.disavowed_domains):
                lines.append(f"domain:{domain}")
            lines.append("")
        
        # Add disavowed URLs
        if self.disavowed_urls:
            lines.append("# Disavowed URLs")
            for url in sorted(self.disavowed_urls):
                lines.append(url)
        
        return "\n".join(lines)
    
    def export_disavow_file(self, filepath: str) -> None:
        """
        Export disavow file to disk.
        
        Args:
            filepath: Path to export disavow file
        """
        content = self.create_disavow_file()
        with open(filepath, 'w') as f:
            f.write(content)
    
    def get_disavow_summary(self) -> Dict:
        """
        Get summary of disavowed items.
        
        Returns:
            Dictionary with disavow summary
        """
        return {
            "domain": self.domain,
            "total_disavowed_urls": len(self.disavowed_urls),
            "total_disavowed_domains": len(self.disavowed_domains),
            "total_disavowed_items": len(self.disavowed_urls) + len(self.disavowed_domains),
            "disavowed_urls": self.disavowed_urls,
            "disavowed_domains": self.disavowed_domains,
            "last_updated": datetime.now().isoformat()
        }
    
    def export_summary(self, filepath: str) -> None:
        """
        Export disavow summary to JSON file.
        
        Args:
            filepath: Path to export JSON file
        """
        with open(filepath, 'w') as f:
            json.dump(self.get_disavow_summary(), f, indent=2)
