"""Attached files cleaner validator for removing attachment mentions from prompts."""

import re
from typing import Any, Dict, List, Tuple

from urarovite.validators.base import BaseValidator, ValidationResult
from urarovite.core.exceptions import ValidationError


class AttachedFilesCleanerValidator(BaseValidator):
    """Validator to detect and clean attached file mentions while preserving grammar."""

    def __init__(self):
        """Initialize the attached files cleaner validator."""
        super().__init__(
            validator_id="attached_files_cleaner",
            name="Clean Attached Files Mentions",
            description="Detects 'attached file(s)' mentions in prompt text, removes them and adds editor note while preserving grammar.",
        )

    # File type patterns for matching various attachment types
    FILE_TYPES = r'(?:file|files|document|documents|doc|docs|spreadsheet|spreadsheets|sheet|sheets|workbook|report|reports?|csv|excel|pdf|docx|google\s+sheet|google\s+sheets)'
    
    # Grammar-preserving replacement rules (apply in order)
    ATTACHMENT_PATTERNS = [
        # Rule 1: Modifier on a noun (safest case) - drop "attached" only
        {
            'pattern': rf'\b(the|this|that|these|those)\s+attached\s+({FILE_TYPES})\b',
            'replacement': r'\1 \2',
            'description': 'Remove "attached" from determiner + attached + noun'
        },
        
        # Rule 2a: "Please find attached" - convert to proper instruction
        {
            'pattern': r'\bplease\s+find\s+attached\s+',
            'replacement': 'Please review ',
            'description': 'Convert "Please find attached" to "Please review"'
        },
        
        # Rule 2b: Other "see attached" phrases
        {
            'pattern': r'\b(?:please\s+)?see\s+attached\b\.?',
            'replacement': 'see the files',
            'description': 'Replace "see attached" phrases'
        },
        
        # Rule 3: Preposition + attached + noun - drop "attached"
        {
            'pattern': rf'\b(in|from|with|under|on)\s+the\s+attached\s+({FILE_TYPES})\b',
            'replacement': r'\1 the \2',
            'description': 'Remove "attached" from preposition phrases'
        },
        
        # Rule 4: Bare "the attached" (no noun) - replace with "the file(s)"
        {
            'pattern': r'\bthe\s+attached\b(?!\s+\w)',
            'replacement': 'the files',
            'description': 'Replace bare "the attached" with "the files"'
        },
        
        # Rule 5: Lists and coordinated nouns - delete only "attached"
        {
            'pattern': rf'\battached\s+({FILE_TYPES})\s+and\s+({FILE_TYPES})\b',
            'replacement': r'\1 and \2',
            'description': 'Remove "attached" from coordinated file lists'
        },
        
        # Rule 6: Specific file-type mentions - remove "attached" 
        {
            'pattern': rf'\battached\s+({FILE_TYPES})\b',
            'replacement': r'\1',
            'description': 'Remove "attached" from file type mentions'
        },
        
        # Rule 6b: Catch "attached" + multi-word file types (like "Google Sheet")
        {
            'pattern': r'\battached\s+((?:google\s+)?(?:sheet|sheets|spreadsheet|document|file|report))\b',
            'replacement': r'\1',
            'description': 'Remove "attached" from compound file type mentions'
        },
        
        # Rule 7: Parenthetical/label mentions
        {
            'pattern': r'\s*\(attached\)|\s*\[attached\]',
            'replacement': '',
            'description': 'Remove parenthetical attachment mentions'
        },
        
        # Rule 8: Title/caption lines
        {
            'pattern': r'^attached:\s*|^\[attached\]\s*',
            'replacement': '',
            'description': 'Remove attachment prefixes from titles'
        },
        
        # Rule 9: Deictic adverbs with "attached"
        {
            'pattern': r'\bsee\s+the\s+attached\s+(below|above)\b',
            'replacement': r'see \1',
            'description': 'Replace "see the attached below/above" with directional only'
        },
        {
            'pattern': r'\battached\s+(below|above)\b',
            'replacement': r'\1',
            'description': 'Replace "attached below/above" with directional only'
        },
        
        # Rule 10: "that I have attached" - past tense
        {
            'pattern': r'\bthat\s+I\s+have\s+attached\b',
            'replacement': 'provided',
            'description': 'Replace "that I have attached" with "provided"'
        },
        
        # Rule 11: "data attached in" - no article
        {
            'pattern': r'\bdata\s+attached\s+in\s+',
            'replacement': 'data in ',
            'description': 'Remove "attached" from "data attached in"'
        },
        
        # Rule 12: "Attached is" sentence starters
        {
            'pattern': r'^attached\s+is\s+',
            'replacement': 'Here is ',
            'description': 'Replace "Attached is" with "Here is"'
        },
        
        # Rule 13: Complex compound names with "attached"
        {
            'pattern': r'\bthe\s+attached\s+([A-Z][A-Za-z\s]+(?:analysis|report|spreadsheet|document))\b',
            'replacement': r'the \1',
            'description': 'Remove "attached" from compound document names'
        },
        
        # Rule 14: General "attached" pattern (will be filtered by _should_skip_mention)
        {
            'pattern': r'\battached\s+to\s+',
            'replacement': '',
            'description': 'Catch general attached patterns for filtering'
        },
    ]
    
    def _should_skip_mention(self, text: str, start: int, end: int) -> bool:
        """Check if an 'attached' mention should be skipped (not a file attachment)."""
        matched_text = text[start:end].lower()
        
        # Skip "attached to each other" and similar non-file uses
        if 'attached to each other' in text[max(0, start-10):min(len(text), end+10)].lower():
            return True
        if 'attached to' in matched_text and 'other' in text[end:end+20].lower():
            return True
            
        return False

    def validate(
        self,
        sheets_service: Any,
        sheet_id: str,
        mode: str = "flag",
        target_column: str = "prompt",
        **kwargs
    ) -> Dict[str, Any]:
        """Validate and optionally clean attached file mentions in prompts.

        Args:
            sheets_service: Google Sheets service instance
            sheet_id: ID of the sheet to validate
            mode: Either "flag" or "fix"
            target_column: Column to check for attachment mentions (default: "prompt")

        Returns:
            Dictionary containing validation results
        """
        result = ValidationResult()

        try:
            # Get all data from the sheet
            all_data, sheet_name = self._get_all_sheet_data(sheets_service, sheet_id)

            if not all_data:
                result.add_error("Sheet is empty or could not be read")
                return result.to_dict()

            headers = all_data[0]
            data_rows = all_data[1:]

            # Find the target column index
            try:
                target_col_index = headers.index(target_column)
            except ValueError:
                result.add_error(f"Column '{target_column}' not found in sheet")
                return result.to_dict()

            prompts_with_attachments = []
            cleaned_prompts = []

            # Check each row for attachment mentions
            for row_index, row in enumerate(data_rows, start=2):  # Start at 2 for sheet row numbering
                if target_col_index >= len(row):
                    continue  # Skip rows without enough columns

                prompt_text = row[target_col_index].strip()
                if not prompt_text:
                    continue

                # Find attachment mentions
                mentions = self._find_attachment_mentions(prompt_text)

                if mentions:
                    if mode == "flag":
                        prompts_with_attachments.append({
                            "row": row_index,
                            "original_text": prompt_text,
                            "mentions": [m["matched_text"] for m in mentions]
                        })
                        result.add_issue()
                    else:  # fix mode
                        cleaned_text, editor_note = self._clean_attachment_mentions(prompt_text, mentions)
                        
                        # Update the row data
                        updated_row = row.copy()
                        if len(updated_row) <= target_col_index:
                            # Extend row if needed
                            updated_row.extend([''] * (target_col_index + 1 - len(updated_row)))
                        updated_row[target_col_index] = cleaned_text

                        cleaned_prompts.append({
                            "row": row_index,
                            "original_text": prompt_text,
                            "cleaned_text": cleaned_text.split('\n')[0],  # First line only
                            "editor_note": editor_note,
                            "mentions_removed": [m["matched_text"] for m in mentions]
                        })

                        # Replace the row in our data
                        data_rows[row_index - 2] = updated_row
                        result.add_fix()

            if mode == "fix" and result.fixes_applied > 0:
                # Update the sheet with cleaned data
                updated_data = [headers] + data_rows
                self._update_sheet_data(sheets_service, sheet_id, sheet_name, updated_data)

            # Set results
            if mode == "flag":
                result.details["prompts_with_attachments"] = prompts_with_attachments
                if result.issues_found == 0:
                    result.set_automated_log("No attached file mentions found")
                else:
                    result.set_automated_log(f"Found {result.issues_found} prompts with attached file mentions")
            else:
                result.details["cleaned_prompts"] = cleaned_prompts
                if result.fixes_applied == 0:
                    result.set_automated_log("No attached file mentions found")
                else:
                    result.set_automated_log(f"Cleaned {result.fixes_applied} prompts with attached file mentions")

        except ValidationError:
            raise
        except Exception as e:
            result.add_error(f"Unexpected error: {str(e)}")
            result.set_automated_log("Validation failed")

        return result.to_dict()

    def _find_attachment_mentions(self, text: str) -> List[Dict[str, Any]]:
        """Find all attachment mentions in text using grammar-aware patterns.

        Args:
            text: Text to search

        Returns:
            List of dictionaries with mention details
        """
        mentions = []

        for pattern_info in self.ATTACHMENT_PATTERNS:
            pattern = pattern_info['pattern']
            matches = re.finditer(pattern, text, re.IGNORECASE)
            
            for match in matches:
                # Check if this mention should be skipped (not a file attachment)
                if self._should_skip_mention(text, match.start(), match.end()):
                    continue
                    
                mentions.append({
                    "matched_text": match.group(),
                    "start_pos": match.start(),
                    "end_pos": match.end(),
                    "pattern": pattern,
                    "description": pattern_info['description']
                })

        # Sort by position and remove overlaps (keep longest match)
        mentions.sort(key=lambda x: (x["start_pos"], -(x["end_pos"] - x["start_pos"])))
        
        filtered_mentions = []
        for mention in mentions:
            # Check if this mention overlaps with any already added
            overlaps = False
            for existing in filtered_mentions:
                if (mention["start_pos"] < existing["end_pos"] and 
                    mention["end_pos"] > existing["start_pos"]):
                    overlaps = True
                    break
            
            if not overlaps:
                filtered_mentions.append(mention)

        return filtered_mentions

    def _clean_attachment_mentions(self, text: str, mentions: List[Dict[str, Any]]) -> Tuple[str, str]:
        """Clean attachment mentions from text while preserving grammar.

        Args:
            text: Original text
            mentions: List of mentions to clean

        Returns:
            Tuple of (cleaned_text, editor_note)
        """
        if not mentions:
            return text, ""

        cleaned_text = text
        removed_phrases = []

        # Apply pattern replacements in order
        for pattern_info in self.ATTACHMENT_PATTERNS:
            pattern = pattern_info['pattern']
            replacement = pattern_info['replacement']
            
            if callable(replacement):
                # Handle lambda replacements
                def replace_func(match):
                    return replacement(match)
                cleaned_text = re.sub(pattern, replace_func, cleaned_text, flags=re.IGNORECASE)
            else:
                # Handle string replacements
                cleaned_text = re.sub(pattern, replacement, cleaned_text, flags=re.IGNORECASE)

        # Grammar cleanup (Rule 10)
        cleaned_text = self._apply_grammar_cleanup(cleaned_text)

        # Generate editor note
        if len(mentions) == 1:
            editor_note = "[Editor note: Removed reference to attachments to keep the prompt self-contained.]"
        else:
            editor_note = f"[Editor note: Removed {len(mentions)} references to attachments to keep the prompt self-contained.]"

        # Add editor note to the cleaned text
        final_text = f"{cleaned_text}\n\n{editor_note}"

        return final_text, editor_note

    def _apply_grammar_cleanup(self, text: str) -> str:
        """Apply grammar cleanup after attachment removal.

        Args:
            text: Text after attachment removal

        Returns:
            Grammar-corrected text
        """
        # Fix double spaces
        text = re.sub(r'\s+', ' ', text)
        
        # Fix dangling articles and prepositions
        text = re.sub(r'\b(refer to|check|review|see|update|clean)\s+the\s*\.', r'\1 it.', text)
        text = re.sub(r'\b(in|from|with)\s+the\s*\.', r'in it.', text)
        
        # Fix orphaned determiners
        text = re.sub(r'\bthe\s*\.\s*', '. ', text)
        text = re.sub(r'\bthe\s*,\s*', ', ', text)
        
        # Fix sentence spacing
        text = re.sub(r'\.\s*\.\s*', '. ', text)
        text = re.sub(r',\s*,\s*', ', ', text)
        
        # Trim and clean up
        text = text.strip()
        
        return text
