"""
Flexible date match strategy - finds dates with year-month or nearby date matching.
"""

import re
from datetime import datetime
from .base import BaseMatchStrategy
from ..models import TokenLike, Match
from ..token_index import TokenIndex
from ..context import find_context_keywords, CONTEXT_KEYWORDS
from ..utils import DATE_PATTERN


class FlexibleDateMatcher(BaseMatchStrategy):
    """
    Flexible date matching when exact match fails.

    Strategies:
    1. Year-month match: If CSV has 2026-01-15, match any 2026-01-XX date
    2. Nearby date match: Match dates within 7 days of CSV value
    3. Heuristic selection: Use context keywords to select the best date

    This handles cases where CSV InvoiceDate doesn't exactly match PDF,
    but we can still find a reasonable date to label.
    """

    def find_matches(
        self,
        tokens: list[TokenLike],
        value: str,
        field_name: str,
        token_index: TokenIndex | None = None
    ) -> list[Match]:
        """Find flexible date matches."""
        matches = []

        # Parse the target date from normalized values
        target_date = None

        # Try to parse YYYY-MM-DD format
        date_match = re.match(r'^(\d{4})-(\d{2})-(\d{2})$', value)
        if date_match:
            try:
                target_date = datetime(
                    int(date_match.group(1)),
                    int(date_match.group(2)),
                    int(date_match.group(3))
                )
            except ValueError:
                pass

        if not target_date:
            return matches

        # Find all date-like tokens in the document
        date_candidates = []

        for token in tokens:
            token_text = token.text.strip()

            # Search for date pattern in token (use pre-compiled pattern)
            for match in DATE_PATTERN.finditer(token_text):
                try:
                    found_date = datetime(
                        int(match.group(1)),
                        int(match.group(2)),
                        int(match.group(3))
                    )
                    date_str = match.group(0)

                    # Calculate date difference
                    days_diff = abs((found_date - target_date).days)

                    # Check for context keywords
                    context_keywords, context_boost = find_context_keywords(
                        tokens, token, field_name, self.context_radius, token_index
                    )

                    # Check if keyword is in the same token
                    token_lower = token_text.lower()
                    inline_keywords = []
                    for keyword in CONTEXT_KEYWORDS.get(field_name, []):
                        if keyword in token_lower:
                            inline_keywords.append(keyword)

                    date_candidates.append({
                        'token': token,
                        'date': found_date,
                        'date_str': date_str,
                        'matched_text': token_text,
                        'days_diff': days_diff,
                        'context_keywords': context_keywords + inline_keywords,
                        'context_boost': context_boost + (0.1 if inline_keywords else 0),
                        'same_year_month': (found_date.year == target_date.year and
                                           found_date.month == target_date.month),
                    })
                except ValueError:
                    continue

        if not date_candidates:
            return matches

        # Score and rank candidates
        for candidate in date_candidates:
            score = 0.0

            # Strategy 1: Same year-month gets higher score
            if candidate['same_year_month']:
                score = 0.7
                # Bonus if day is close
                if candidate['days_diff'] <= 7:
                    score = 0.75
                if candidate['days_diff'] <= 3:
                    score = 0.8
            # Strategy 2: Nearby dates (within 14 days)
            elif candidate['days_diff'] <= 14:
                score = 0.6
            elif candidate['days_diff'] <= 30:
                score = 0.55
            else:
                # Too far apart, skip unless has strong context
                if not candidate['context_keywords']:
                    continue
                score = 0.5

            # Strategy 3: Boost with context keywords
            score = min(1.0, score + candidate['context_boost'])

            # For InvoiceDate, prefer dates that appear near invoice-related keywords
            # For InvoiceDueDate, prefer dates near due-date keywords
            if candidate['context_keywords']:
                score = min(1.0, score + 0.05)

            if score >= 0.5:  # Min threshold for flexible matching
                matches.append(Match(
                    field=field_name,
                    value=candidate['date_str'],
                    bbox=candidate['token'].bbox,
                    page_no=candidate['token'].page_no,
                    score=score,
                    matched_text=candidate['matched_text'],
                    context_keywords=candidate['context_keywords']
                ))

        # Sort by score and return best matches
        matches.sort(key=lambda m: m.score, reverse=True)

        # Only return the best match to avoid multiple labels for same field
        return matches[:1] if matches else []
