"""
Law School Study Aid Tools
Flash card generation, outline building, and exam preparation
"""

import re
from typing import Dict, List, Tuple, Optional
from dataclasses import dataclass
from collections import defaultdict


# ============================================================================
# FLASH CARD GENERATION
# ============================================================================

@dataclass
class FlashCard:
    """A single flash card for study"""
    front: str  # Question
    back: str  # Answer
    category: str  # Topic area
    source: str  # Case name or source
    tags: List[str]  # Additional tags for organization


def generate_flash_cards_from_brief(case_brief: Dict) -> List[FlashCard]:
    """
    Generate flash cards from a case brief.

    Creates cards for:
    - Facts → Issue
    - Issue → Holding
    - Facts → Rule
    - Rule → Application
    """
    cards = []
    case_name = case_brief.get('case_name', 'Unknown Case')
    citation = case_brief.get('citation', '')

    # Card 1: Facts to Issue
    if case_brief.get('facts') and case_brief.get('issues'):
        facts_summary = ' '.join(case_brief['facts'][:2])  # First 2 fact sentences
        issue = case_brief['issues'][0] if case_brief['issues'] else ""

        if facts_summary and issue:
            cards.append(FlashCard(
                front=f"In {case_name}, {facts_summary}\n\nWhat was the issue?",
                back=issue,
                category="Case Analysis",
                source=f"{case_name}, {citation}",
                tags=["facts-to-issue", "case-law"]
            ))

    # Card 2: Issue to Holding
    if case_brief.get('issues') and case_brief.get('holdings'):
        issue = case_brief['issues'][0] if case_brief['issues'] else ""
        holding = case_brief['holdings'][0] if case_brief['holdings'] else ""

        if issue and holding:
            cards.append(FlashCard(
                front=f"{case_name}: {issue}",
                back=f"Holding: {holding}",
                category="Holdings",
                source=f"{case_name}, {citation}",
                tags=["issue-to-holding", "case-law"]
            ))

    # Card 3: Rule statement
    if case_brief.get('rules'):
        for rule in case_brief['rules'][:3]:  # Up to 3 rule cards
            cards.append(FlashCard(
                front=f"What is the rule from {case_name}?",
                back=rule,
                category="Legal Rules",
                source=f"{case_name}, {citation}",
                tags=["rules", "case-law"]
            ))

    # Card 4: Case name to holding (for quick review)
    if case_brief.get('holdings'):
        holding = case_brief['holdings'][0] if case_brief['holdings'] else ""
        if holding:
            cards.append(FlashCard(
                front=f"What did the court hold in {case_name}?",
                back=holding,
                category="Case Holdings",
                source=f"{case_name}, {citation}",
                tags=["case-name-to-holding", "quick-review"]
            ))

    return cards


def generate_rule_flash_cards(text: str) -> List[FlashCard]:
    """
    Generate flash cards for legal rules found in text.

    Looks for:
    - "The test is..."
    - "The standard requires..."
    - "To establish X, plaintiff must prove..."
    - "The elements are..."
    """
    cards = []

    # Pattern for tests/standards
    test_pattern = re.compile(
        r'(?:The |This )?(?:test|standard|rule)(?:\s+(?:is|requires|provides))?\s+(.{20,200})\.',
        re.IGNORECASE
    )

    for match in test_pattern.finditer(text):
        test_text = match.group(1).strip()

        cards.append(FlashCard(
            front=f"What is the test/standard?",
            back=test_text,
            category="Legal Tests",
            source="Document",
            tags=["rules", "tests"]
        ))

    # Pattern for elements
    elements_pattern = re.compile(
        r'(?:elements|factors|requirements|prongs)\s+(?:are|of)\s+(.{30,300})',
        re.IGNORECASE
    )

    for match in elements_pattern.finditer(text):
        elements_text = match.group(1).strip()

        # Try to extract list
        elements = extract_numbered_list(elements_text)

        if elements:
            elements_formatted = '\n'.join(f"{i+1}. {elem}" for i, elem in enumerate(elements))

            cards.append(FlashCard(
                front=f"What are the elements/factors?",
                back=elements_formatted,
                category="Legal Elements",
                source="Document",
                tags=["elements", "tests"]
            ))

    return cards[:10]  # Limit to 10 cards


def extract_numbered_list(text: str) -> List[str]:
    """Extract a numbered or bulleted list from text"""
    # Look for (1), (2), etc.
    paren_pattern = re.compile(r'\((\d+)\)\s*([^;.]+)[;.]')
    matches = paren_pattern.findall(text)

    if matches:
        return [m[1].strip() for m in matches]

    # Look for 1., 2., etc.
    number_pattern = re.compile(r'(\d+)\.\s*([^;.]+)[;.]')
    matches = number_pattern.findall(text)

    if matches:
        return [m[1].strip() for m in matches]

    return []


def format_flash_cards_anki(cards: List[FlashCard]) -> str:
    """
    Format flash cards for import into Anki.

    Format: Front;Back;Tags
    """
    lines = []

    for card in cards:
        # Escape semicolons in text
        front = card.front.replace(';', ',')
        back = card.back.replace(';', ',')
        tags = ' '.join(card.tags)

        lines.append(f"{front};{back};{tags}")

    return '\n'.join(lines)


def format_flash_cards_quizlet(cards: List[FlashCard]) -> str:
    """
    Format flash cards for import into Quizlet.

    Format: Front\tBack
    """
    lines = []

    for card in cards:
        front = card.front.replace('\t', ' ')
        back = card.back.replace('\t', ' ')

        lines.append(f"{front}\t{back}")

    return '\n'.join(lines)


# ============================================================================
# OUTLINE BUILDER
# ============================================================================

@dataclass
class OutlineSection:
    """A section in a course outline"""
    level: int  # 1 = topic, 2 = subtopic, etc.
    title: str
    content: List[str]
    rules: List[str]
    cases: List[str]  # Case names
    examples: List[str]


def build_course_outline(documents: List[Dict[str, any]], topic: str = "") -> str:
    """
    Build a course outline from multiple case briefs and readings.

    Args:
        documents: List of dictionaries containing case briefs or readings
        topic: Course topic (e.g., "Civil Procedure", "Torts")

    Returns:
        Formatted outline as string
    """
    output = []

    if topic:
        output.append(f"{topic.upper()} - COURSE OUTLINE")
    else:
        output.append("COURSE OUTLINE")

    output.append("=" * 80)
    output.append("")

    # Group by topics
    topic_groups = group_by_topics(documents)

    for topic_name, docs in topic_groups.items():
        output.append(f"\n{'='*80}")
        output.append(f"{topic_name}")
        output.append(f"{'='*80}\n")

        # Extract rules from this topic
        rules = set()
        cases = []

        for doc in docs:
            if 'rules' in doc:
                rules.update(doc['rules'])

            if 'case_name' in doc:
                cases.append(doc['case_name'])

        # Rules section
        if rules:
            output.append("  RULES:")
            for i, rule in enumerate(sorted(rules), 1):
                output.append(f"    {i}. {rule}")
            output.append("")

        # Cases section
        if cases:
            output.append("  KEY CASES:")
            for case in cases:
                # Find the brief for this case
                case_doc = next((d for d in docs if d.get('case_name') == case), None)

                if case_doc:
                    holding = case_doc.get('holdings', [''])[0] if case_doc.get('holdings') else ""

                    output.append(f"    • {case}")
                    if holding:
                        output.append(f"      → {holding[:150]}...")
                else:
                    output.append(f"    • {case}")

            output.append("")

    return '\n'.join(output)


def group_by_topics(documents: List[Dict]) -> Dict[str, List[Dict]]:
    """Group documents by legal topics"""
    from legal_analysis import identify_primary_topics

    topic_groups = defaultdict(list)

    for doc in documents:
        # Get text from document
        text = ""
        if 'facts' in doc:
            text += ' '.join(doc['facts'])
        if 'issues' in doc:
            text += ' '.join(doc['issues'])
        if 'reasoning' in doc:
            text += ' '.join(doc['reasoning'])

        if text:
            topics = identify_primary_topics(text, top_n=1)

            if topics:
                primary_topic = topics[0][0]
                topic_groups[primary_topic].append(doc)
            else:
                topic_groups["Other"].append(doc)
        else:
            topic_groups["Other"].append(doc)

    return dict(topic_groups)


# ============================================================================
# EXAM HYPOTHETICAL MATCHER
# ============================================================================

def match_hypo_to_cases(
    hypothetical: str,
    case_briefs: List[Dict]
) -> List[Tuple[str, float, List[str]]]:
    """
    Match an exam hypothetical to relevant cases.

    Args:
        hypothetical: The exam question/hypothetical text
        case_briefs: List of case brief dictionaries

    Returns:
        List of (case_name, relevance_score, matching_keywords) tuples
    """
    hypo_lower = hypothetical.lower()

    # Extract keywords from hypothetical
    hypo_keywords = extract_legal_keywords(hypo_lower)

    results = []

    for brief in case_briefs:
        case_name = brief.get('case_name', 'Unknown')

        # Get all text from the brief
        brief_text = ""
        for field in ['facts', 'issues', 'holdings', 'reasoning', 'rules']:
            if field in brief:
                if isinstance(brief[field], list):
                    brief_text += ' '.join(brief[field])
                else:
                    brief_text += str(brief[field])

        brief_lower = brief_text.lower()

        # Count matching keywords
        matching_keywords = []
        score = 0

        for keyword in hypo_keywords:
            if keyword in brief_lower:
                matching_keywords.append(keyword)
                score += 1

        # Bonus for exact fact pattern matches
        if has_similar_facts(hypothetical, brief.get('facts', [])):
            score += 5

        if score > 0:
            results.append((case_name, score, matching_keywords))

    # Sort by relevance score
    results.sort(key=lambda x: x[1], reverse=True)

    return results


def extract_legal_keywords(text: str) -> Set[str]:
    """Extract important legal keywords from text"""
    # Common legal terms to look for
    legal_terms = {
        'negligence', 'duty', 'breach', 'causation', 'damages',
        'assault', 'battery', 'intent', 'reckless',
        'contract', 'offer', 'acceptance', 'consideration',
        'jurisdiction', 'venue', 'standing', 'ripeness',
        'search', 'seizure', 'warrant', 'probable cause',
        'miranda', 'confession', 'suppression',
        'statute of limitations', 'discovery', 'summary judgment',
        'hearsay', 'relevance', 'prejudice',
        'reasonable', 'foreseeable', 'proximate',
    }

    found_terms = set()

    for term in legal_terms:
        if term in text:
            found_terms.add(term)

    # Also extract proper nouns (potential party types)
    proper_noun_pattern = re.compile(r'\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\b')
    for match in proper_noun_pattern.finditer(text):
        word = match.group(0).lower()
        if word not in {'the', 'a', 'an', 'in', 'on', 'at'}:
            found_terms.add(word)

    return found_terms


def has_similar_facts(hypo: str, case_facts: List[str]) -> bool:
    """Check if hypothetical has similar facts to case"""
    if not case_facts:
        return False

    hypo_lower = hypo.lower()
    case_text = ' '.join(case_facts).lower()

    # Look for similar fact patterns
    fact_indicators = [
        'plaintiff', 'defendant', 'injury', 'accident',
        'agreement', 'contract', 'signed', 'paid',
        'arrested', 'searched', 'questioned', 'charged',
    ]

    common_indicators = 0
    for indicator in fact_indicators:
        if indicator in hypo_lower and indicator in case_text:
            common_indicators += 1

    return common_indicators >= 2


# ============================================================================
# PRACTICE PROBLEM GENERATOR
# ============================================================================

def generate_practice_problems(case_briefs: List[Dict]) -> List[Dict[str, str]]:
    """
    Generate practice problems from case briefs.

    Returns:
        List of problems with 'question', 'answer', 'case_source'
    """
    problems = []

    for brief in case_briefs:
        case_name = brief.get('case_name', 'Unknown Case')
        facts = brief.get('facts', [])
        issues = brief.get('issues', [])
        holdings = brief.get('holdings', [])
        rules = brief.get('rules', [])

        # Problem 1: Change one fact and ask about outcome
        if facts and holdings:
            fact_base = facts[0] if facts else ""
            holding = holdings[0] if holdings else ""

            problem = {
                'question': f"Based on {case_name}, if instead {fact_base.replace('the', 'a different')} "
                           f"what would the likely outcome be?",
                'answer': f"Original holding was: {holding}. The changed facts would likely "
                         f"affect the analysis because...",
                'case_source': case_name,
                'type': 'fact_variation'
            }
            problems.append(problem)

        # Problem 2: Application problem
        if rules and issues:
            rule = rules[0] if rules else ""
            issue = issues[0] if issues else ""

            problem = {
                'question': f"Apply the rule from {case_name}: {rule}\n\n"
                           f"Hypothetical: A similar situation arises. How would you analyze it?",
                'answer': f"Using the {case_name} framework: 1) Identify the elements, "
                         f"2) Apply facts to each element, 3) Conclude",
                'case_source': case_name,
                'type': 'rule_application'
            }
            problems.append(problem)

    return problems


def format_practice_problems(problems: List[Dict[str, str]]) -> str:
    """Format practice problems for printing/studying"""
    output = []
    output.append("PRACTICE PROBLEMS")
    output.append("=" * 80)
    output.append("")

    for i, problem in enumerate(problems, 1):
        output.append(f"Problem {i} (from {problem['case_source']})")
        output.append("-" * 80)
        output.append(f"Question: {problem['question']}")
        output.append("")
        output.append(f"Suggested Answer: {problem['answer']}")
        output.append("")
        output.append("")

    return '\n'.join(output)
