"""Empty and invalid ranges validator for Google Sheets.

This validator checks whether A1 ranges in Google Sheets are valid and non-empty
using the Google Sheets API.
"""

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

from urarovite.validators.base import BaseValidator, ValidationResult
from urarovite.core.exceptions import ValidationError
from urarovite.utils.sheets import extract_sheet_id, get_sheet_values


class EmptyInvalidRangesValidator(BaseValidator):
    """Validator for checking empty and invalid ranges in Google Sheets."""

    # Regex patterns for A1 geometry sanity checks
    _A1_RECT_RE = re.compile(r"^([A-Za-z]+)(\d+):([A-Za-z]+)(\d+)$")
    _A1_CELL_RE = re.compile(r"^([A-Za-z]+)(\d+)$")
    _A1_COL_RE = re.compile(r"^([A-Za-z]+):([A-Za-z]+)$")
    _A1_ROW_RE = re.compile(r"^(\d+):(\d+)$")
    


    def __init__(self) -> None:
        super().__init__(
            validator_id="empty_invalid_ranges",
            name="Check Empty and Invalid Ranges",
            description="Validates that A1 ranges are valid and contain non-empty data",
        )

    def validate(
        self,
        sheets_service: Any,
        sheet_id: str,
        mode: str,
        range_columns: List[int] = None,
        url_columns: List[int] = None,
        **kwargs: Any,
    ) -> Dict[str, Any]:
        """Validate ranges for emptiness and invalidity.

        Args:
            sheets_service: Google Sheets API service instance
            sheet_id: ID of the spreadsheet to validate
            mode: Either "fix" (not applicable) or "flag" (report only)
            range_columns: List of 1-based column indices containing A1 ranges
            url_columns: List of 1-based column indices containing spreadsheet URLs

        Returns:
            Dict with validation results
        """
        result = ValidationResult()

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

            if not data:
                result.add_error("Sheet is empty")
                return result.to_dict()

            # Auto-detect range and URL columns if not specified
            if range_columns is None:
                range_columns = self._detect_range_columns(data)
            if url_columns is None:
                url_columns = self._detect_url_columns(data)

            if not range_columns or not url_columns:
                result.details["message"] = "No range or URL columns found in data"
                result.details["valid_ranges"] = 0
                result.details["invalid_ranges"] = []
                return result.to_dict()

            invalid_ranges = []
            valid_count = 0

            # Check each range in the specified columns
            for row_idx, row in enumerate(data):
                if row_idx == 0:  # Skip header row
                    continue

                # Get range and URL from the row
                range_value = None
                url_value = None

                for col_idx in range_columns:
                    if col_idx <= len(row) and row[col_idx - 1]:
                        range_value = str(row[col_idx - 1]).strip()
                        break

                for col_idx in url_columns:
                    if col_idx <= len(row) and row[col_idx - 1]:
                        url_value = str(row[col_idx - 1]).strip()
                        break

                if range_value and url_value:
                    validation_result = self._validate_sheet_range(
                        sheets_service, range_value, url_value
                    )

                    if not validation_result[0]:  # Not valid
                        invalid_ranges.append({
                            "row": row_idx + 1,
                            "range": range_value,
                            "url": url_value,
                            "error": validation_result[1],
                        })
                        result.add_issue()
                    else:
                        valid_count += 1

            # Record results
            if invalid_ranges:
                result.details["invalid_ranges"] = invalid_ranges
                result.details["valid_ranges"] = valid_count
                result.set_automated_log(f"Invalid ranges: {len(invalid_ranges)} found")
            else:
                result.details["valid_ranges"] = valid_count
                result.details["message"] = "All ranges are valid and non-empty"
                result.set_automated_log("No issues found")

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

        return result.to_dict()

    def _col_to_num(self, col: str) -> int:
        """Convert column letter(s) to number."""
        n = 0
        for ch in col.upper():
            if not ('A' <= ch <= 'Z'):
                return -1
            n = n * 26 + (ord(ch) - ord('A') + 1)
        return n

    def _zero_sized(self, a1: str) -> bool:
        """Detect 0-sized/invalid geometry from common A1 forms."""
        m = self._A1_RECT_RE.match(a1)
        if m:
            c1, r1, c2, r2 = m.groups()
            c1n, c2n = self._col_to_num(c1), self._col_to_num(c2)
            r1n, r2n = int(r1), int(r2)
            return c1n <= 0 or c2n <= 0 or r1n <= 0 or r2n <= 0 or (c2n - c1n + 1) <= 0 or (r2n - r1n + 1) <= 0

        m = self._A1_COL_RE.match(a1)
        if m:
            c1, c2 = m.groups()
            c1n, c2n = self._col_to_num(c1), self._col_to_num(c2)
            return c1n <= 0 or c2n <= 0 or (c2n - c1n + 1) <= 0

        m = self._A1_ROW_RE.match(a1)
        if m:
            r1, r2 = map(int, m.groups())
            return r1 <= 0 or r2 <= 0 or (r2 - r1 + 1) <= 0

        # Single-cell or open-ended ranges (A1:B, A:A, 1:), let API decide.
        return False

    def _validate_sheet_range(
        self, 
        sheets_service: Any,
        a1_range: str,
        spreadsheet_url: str
    ) -> Tuple[bool, str]:
        """Validate that `a1_range` exists in the Google Sheet and is NOT empty."""
        # URL sanity
        spreadsheet_id = extract_sheet_id(spreadsheet_url)
        if not spreadsheet_id:
            return (False, "Invalid spreadsheet URL — manual fix required")

        # Range sanity (basic)
        if not isinstance(a1_range, str) or not a1_range.strip():
            return (False, "Range is missing — manual fix required")
        a1_range = a1_range.strip()

        # For geometry checks, isolate the portion after "!" if present.
        pure_range = a1_range.split("!", 1)[1].strip() if "!" in a1_range else a1_range
        if self._zero_sized(pure_range):
            return (False, "Range is invalid or 0-sized — manual fix required")

        # Use the existing utility to get sheet values
        try:
            result = get_sheet_values(sheets_service, spreadsheet_id, a1_range)
            
            if not result["success"]:
                error = result.get("error", "unknown")
                if error == "forbidden_or_not_found":
                    return (False, "No access to the spreadsheet or sheet not found — manual fix required")
                elif "request_exception" in error:
                    return (False, f"Google Sheets API error: {error} — manual fix required")
                else:
                    return (False, f"Failed to access range: {error} — manual fix required")

            values = result.get("values", [])
            # Check if any cell contains non-empty data
            non_empty = any(
                (cell is not None) and (str(cell).strip() != "") 
                for row in values 
                for cell in row
            )

            if not non_empty:
                return (False, "Range is empty — manual fix required")

            return (True, "Range is valid and not empty")

        except Exception as e:
            return (False, f"Unexpected error: {e.__class__.__name__} — manual fix required")

    def _detect_range_columns(self, data: List[List[Any]]) -> List[int]:
        """Auto-detect columns containing A1 ranges."""
        range_columns = []

        if not data:
            return range_columns

        # Check first few rows for A1 range patterns
        sample_rows = data[:min(10, len(data))]

        for col_idx in range(len(data[0]) if data[0] else 0):
            contains_ranges = False

            for row in sample_rows:
                if col_idx < len(row) and row[col_idx]:
                    cell_value = str(row[col_idx]).strip()
                    # Look for A1 notation patterns
                    if ("!" in cell_value and 
                        (self._A1_RECT_RE.search(cell_value.split("!")[-1]) or
                         self._A1_CELL_RE.search(cell_value.split("!")[-1]) or
                         self._A1_COL_RE.search(cell_value.split("!")[-1]) or
                         self._A1_ROW_RE.search(cell_value.split("!")[-1]))):
                        contains_ranges = True
                        break

            if contains_ranges:
                range_columns.append(col_idx + 1)  # Convert to 1-based

        return range_columns

    def _detect_url_columns(self, data: List[List[Any]]) -> List[int]:
        """Auto-detect columns containing Google Sheets URLs."""
        url_columns = []

        if not data:
            return url_columns

        # Check first few rows for URLs
        sample_rows = data[:min(10, len(data))]

        for col_idx in range(len(data[0]) if data[0] else 0):
            contains_urls = False

            for row in sample_rows:
                if col_idx < len(row) and row[col_idx]:
                    cell_value = str(row[col_idx])
                    # Use the utility function to check if it contains a valid sheet ID
                    if extract_sheet_id(cell_value) is not None:
                        contains_urls = True
                        break

            if contains_urls:
                url_columns.append(col_idx + 1)  # Convert to 1-based

        return url_columns 