import re
from typing import Dict, List, Tuple, Any
from dataclasses import dataclass
import numpy as np

try:
    from sentence_transformers import SentenceTransformer
    from sklearn.metrics.pairwise import cosine_similarity
    TRANSFORMERS_AVAILABLE = True
    print("✅ NLP libraries loaded successfully")
except ImportError as e:
    print(f"⚠️  Warning: Some NLP libraries not available: {e}")
    print("   Falling back to keyword-only matching")
    TRANSFORMERS_AVAILABLE = False

@dataclass
class MatchResult:
    rule_category: str
    rule_type: str
    confidence: float
    matched_phrases: List[str]
    reasoning: str

class NLPMatcher:
    def __init__(self):
        print("🔄 Initializing NLP matcher...")

        if TRANSFORMERS_AVAILABLE:
            try:
                print("   Loading sentence transformer model...")
                self.model = SentenceTransformer('all-MiniLM-L6-v2')
                print("   ✅ Sentence transformer loaded")
            except Exception as e:
                print(f"   ⚠️  Failed to load transformer: {e}")
                print("   Falling back to pattern matching only")
                self.model = None
        else:
            self.model = None

        # Key phrase patterns for quick matching
        print("   Loading pattern matching rules...")
        self.positive_patterns = {
            'music_first': [
                r'\bmusic[- ]first\b', r'\bmusic company\b', r'\bartist empowerment\b',
                r'\bcreativity\b', r'\bimagination\b', r'\bmusic[- ]making\b'
            ],
            'inclusive_tone': [
                r'\binclusive\b', r'\baccessible\b', r'\bcommunity\b', r'\bconnection\b',
                r'\bbelonging\b', r'\bwelcoming\b'
            ],
            'human_centered': [
                r'\bhuman value\b', r'\bself[- ]expression\b', r'\bmeaningful creativity\b',
                r'\bartist\b(?!\s+replacement)', r'\bcollaborat\w+\b'
            ],
            'authentic_voice': [
                r'\bauthentic\b', r'\bgenuine\b', r'\bpoetic\b', r'\blyrical\b',
                r'\bplayful\b', r'\bexperimental\b'
            ]
        }

        self.negative_patterns = {
            'ai_first': [
                r'\bAI[- ]first\b', r'\bAI music company\b', r'\btechnology over\b',
                r'\btech[- ]heavy\b', r'\bAI does everything\b'
            ],
            'replacement_language': [
                r'\breplace\w* (?:human )?artist\w*\b', r'\bmusic is broken\b',
                r'\bmake music great again\b', r'\bdemocratize music\b'
            ],
            'tech_bro': [
                r'\bdisrupt\w*\b', r'\bscale\b', r'\bleverage\b', r'\bsynerg\w+\b',
                r'\boptimize\b', r'\bmonetize\b'
            ],
            'trivializing': [
                r'\beasy\b.*\bmusic\b', r'\beffortless\b.*\bcreation\b',
                r'\bcheap\b.*\bslop\b', r'\bshortcut\b'
            ],
            'salesy_tone': [
                r'\bAMAZING\b', r'\bREVOLUTIONARY\b', r'\bGAME[- ]CHANGER\b',
                r'\bUNLIMITED\b', r'\bFREE FOREVER\b'
            ]
        }
        print("   ✅ Pattern matching rules loaded")

    def analyze_content(self, content: str, approval_rules: List, rejection_rules: List) -> Dict[str, Any]:
        print(f"🔍 Analyzing content: '{content[:50]}{'...' if len(content) > 50 else ''}'")
        content_lower = content.lower()

        # Quick pattern matching
        print("   Checking pattern matches...")
        pattern_matches = self._match_patterns(content_lower)
        print(f"   Found {len(pattern_matches)} pattern matches")

        # Semantic similarity matching
        if self.model is not None:
            print("   Performing semantic analysis...")
            semantic_matches = self._semantic_matching(content, approval_rules, rejection_rules)
            print(f"   Found {len(semantic_matches)} semantic matches")
        else:
            print("   Using keyword fallback matching...")
            semantic_matches = self._keyword_fallback_matching(content, approval_rules, rejection_rules)
            print(f"   Found {len(semantic_matches)} keyword matches")

        # Combine results
        all_matches = pattern_matches + semantic_matches
        print(f"   Total matches: {len(all_matches)}")

        # Calculate overall score
        approval_score = sum(m.confidence for m in all_matches if m.rule_type == 'approve')
        rejection_score = sum(m.confidence for m in all_matches if m.rule_type == 'reject')

        net_score = approval_score - rejection_score
        approved = net_score > 0 and rejection_score < 0.7

        print(f"   📊 Scores - Approval: {approval_score:.2f}, Rejection: {rejection_score:.2f}, Net: {net_score:.2f}")
        print(f"   🎯 Decision: {'APPROVED' if approved else 'REJECTED'}")

        # Generate reasoning
        reasoning = self._generate_reasoning(all_matches, approved, net_score)

        return {
            "approved": approved,
            "approval_score": approval_score,
            "rejection_score": rejection_score,
            "net_score": net_score,
            "matches": all_matches,
            "reasoning": reasoning
        }

    def _match_patterns(self, content: str) -> List[MatchResult]:
        matches = []

        # Check positive patterns
        for category, patterns in self.positive_patterns.items():
            for pattern in patterns:
                if re.search(pattern, content, re.IGNORECASE):
                    matches.append(MatchResult(
                        rule_category=category,
                        rule_type='approve',
                        confidence=0.8,
                        matched_phrases=[pattern],
                        reasoning=f"Contains positive pattern: {pattern}"
                    ))

        # Check negative patterns
        for category, patterns in self.negative_patterns.items():
            for pattern in patterns:
                if re.search(pattern, content, re.IGNORECASE):
                    matches.append(MatchResult(
                        rule_category=category,
                        rule_type='reject',
                        confidence=0.9,
                        matched_phrases=[pattern],
                        reasoning=f"Contains problematic pattern: {pattern}"
                    ))

        return matches

    def _semantic_matching(self, content: str, approval_rules: List, rejection_rules: List) -> List[MatchResult]:
        matches = []

        if not content.strip() or self.model is None:
            return matches

        try:
            # Encode content
            content_embedding = self.model.encode([content])

            # Check approval rules
            for rule in approval_rules:
                rule_phrases = rule.phrases + rule.keywords
                if rule_phrases:
                    phrase_embeddings = self.model.encode(rule_phrases)
                    similarities = cosine_similarity(content_embedding, phrase_embeddings)[0]

                    max_similarity = np.max(similarities)
                    if max_similarity > 0.4:  # Threshold for semantic similarity
                        best_match_idx = np.argmax(similarities)
                        matches.append(MatchResult(
                            rule_category=rule.category,
                            rule_type='approve',
                            confidence=min(max_similarity, 0.9),
                            matched_phrases=[rule_phrases[best_match_idx]],
                            reasoning=f"Semantically similar to approved concept: {rule_phrases[best_match_idx]}"
                        ))

            # Check rejection rules
            for rule in rejection_rules:
                rule_phrases = rule.phrases + rule.keywords
                if rule_phrases:
                    phrase_embeddings = self.model.encode(rule_phrases)
                    similarities = cosine_similarity(content_embedding, phrase_embeddings)[0]

                    max_similarity = np.max(similarities)
                    if max_similarity > 0.5:  # Higher threshold for rejection
                        best_match_idx = np.argmax(similarities)
                        matches.append(MatchResult(
                            rule_category=rule.category,
                            rule_type='reject',
                            confidence=min(max_similarity, 0.95),
                            matched_phrases=[rule_phrases[best_match_idx]],
                            reasoning=f"Semantically similar to rejected concept: {rule_phrases[best_match_idx]}"
                        ))

        except Exception as e:
            print(f"   ⚠️  Semantic matching failed: {e}")
            # Fallback to keyword matching
            return self._keyword_fallback_matching(content, approval_rules, rejection_rules)

        return matches

    def _keyword_fallback_matching(self, content: str, approval_rules: List, rejection_rules: List) -> List[MatchResult]:
        matches = []
        content_lower = content.lower()

        # Simple keyword matching as fallback
        for rule in approval_rules:
            for keyword in rule.keywords:
                if keyword.lower() in content_lower:
                    matches.append(MatchResult(
                        rule_category=rule.category,
                        rule_type='approve',
                        confidence=0.6,
                        matched_phrases=[keyword],
                        reasoning=f"Contains approved keyword: {keyword}"
                    ))

        for rule in rejection_rules:
            for keyword in rule.keywords:
                if keyword.lower() in content_lower:
                    matches.append(MatchResult(
                        rule_category=rule.category,
                        rule_type='reject',
                        confidence=0.7,
                        matched_phrases=[keyword],
                        reasoning=f"Contains problematic keyword: {keyword}"
                    ))

        return matches

    def _generate_reasoning(self, matches: List[MatchResult], approved: bool, net_score: float) -> str:
        if not matches:
            return "No significant matches found against brand guidelines."

        approve_matches = [m for m in matches if m.rule_type == 'approve']
        reject_matches = [m for m in matches if m.rule_type == 'reject']

        reasoning_parts = []

        if approved:
            reasoning_parts.append("✅ APPROVED:")
            if approve_matches:
                top_approve = max(approve_matches, key=lambda x: x.confidence)
                reasoning_parts.append(f"Strong alignment with {top_approve.rule_category}")
            if reject_matches:
                reasoning_parts.append(f"Minor concerns noted but overall positive (score: {net_score:.2f})")
        else:
            reasoning_parts.append("❌ REJECTED:")
            if reject_matches:
                top_reject = max(reject_matches, key=lambda x: x.confidence)
                reasoning_parts.append(f"Violates {top_reject.rule_category} guidelines")
            if approve_matches:
                reasoning_parts.append("Some positive elements found but insufficient to approve")

        # Add specific match details
        if len(matches) <= 3:
            for match in matches[:3]:
                reasoning_parts.append(f"• {match.reasoning}")

        return " ".join(reasoning_parts)