"""
HubSpot data validation module.
Validates data against HubSpot property schemas and business rules.
"""

import logging
import re
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Dict, List, Optional

import pandas as pd

from .core import HubSpotIntegration, HubSpotProperty

logger = logging.getLogger(__name__)


@dataclass
class ValidationIssue:
    """Represents a single validation issue."""

    severity: str  # 'error' or 'warning'
    rule_type: str  # 'required', 'type', 'length', etc.
    field_name: str
    row_index: Optional[int] = None
    value: Any = None
    message: str = ""
    suggested_fix: Optional[str] = None


@dataclass
class ValidationResult:
    """Result of validation run."""

    valid: bool
    total_rows: int
    error_count: int = 0
    warning_count: int = 0
    issues: List[ValidationIssue] = field(default_factory=list)
    validation_duration_seconds: float = 0.0

    def add_issue(self, issue: ValidationIssue):
        """Add a validation issue and update counts."""
        self.issues.append(issue)
        if issue.severity == "error":
            self.error_count += 1
            self.valid = False
        else:
            self.warning_count += 1


@dataclass
class DataQualityReport:
    """Comprehensive data quality metrics."""

    total_records: int
    valid_records: int
    invalid_records: int
    completeness_score: float  # 0-1
    accuracy_score: float  # 0-1
    consistency_score: float  # 0-1
    overall_score: float  # 0-1
    field_metrics: Dict[str, Dict[str, Any]] = field(default_factory=dict)
    duplicate_analysis: Dict[str, Any] = field(default_factory=dict)
    pattern_violations: Dict[str, int] = field(default_factory=dict)


class HubSpotDataValidator:
    """
    Validates data against HubSpot property schemas and business rules.
    """

    # Regex patterns for common formats
    EMAIL_PATTERN = re.compile(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
    PHONE_PATTERN = re.compile(r"^[\+\-\(\)\s\d]+$")
    URL_PATTERN = re.compile(r"^https?://[^\s]+$")
    HUBSPOT_ID_PATTERN = re.compile(r"^\d+$")  # HubSpot IDs are numeric

    # HubSpot-specific read-only system fields
    READ_ONLY_FIELDS = {
        "hs_object_id",
        "createdate",
        "lastmodifieddate",
        "hs_createdate",
        "hs_lastmodifieddate",
        "hs_all_*",
        "hs_analytics_*",
        "hs_calculated_*",
    }

    def __init__(self, integration: HubSpotIntegration):
        self.integration = integration
        self._property_cache: Dict[str, List[HubSpotProperty]] = {}

    async def validate_dataframe(
        self,
        df: pd.DataFrame,
        object_name: str,
        operation: str = "CREATE_AND_UPDATE",
        custom_rules: Optional[List[callable]] = None,
    ) -> ValidationResult:
        """
        Validate a DataFrame against HubSpot object schema.

        Args:
            df: DataFrame to validate
            object_name: HubSpot object type
            operation: Operation type (CREATE_AND_UPDATE, CREATE_ONLY, UPDATE_ONLY)
            custom_rules: Optional list of custom validation functions

        Returns:
            ValidationResult with all issues found
        """
        start_time = datetime.now()
        result = ValidationResult(valid=True, total_rows=len(df))

        # Get property schema
        properties = await self._get_properties(object_name)
        property_map = {prop.name: prop for prop in properties}

        # Run validation rules
        await self._validate_schema(df, property_map, operation, result)
        await self._validate_required_fields(df, property_map, operation, result)
        await self._validate_field_types(df, property_map, result)
        await self._validate_field_lengths(df, property_map, result)
        await self._validate_field_formats(df, property_map, result)
        await self._validate_enumeration_values(df, property_map, result)
        await self._validate_read_only_fields(df, property_map, operation, result)
        await self._validate_hubspot_specific_rules(df, object_name, result)
        await self._validate_duplicates(df, object_name, result)

        # Run custom rules if provided
        if custom_rules:
            for rule_func in custom_rules:
                try:
                    rule_func(df, result)
                except Exception as e:
                    logger.error(f"Custom rule error: {e}")

        # Calculate duration
        result.validation_duration_seconds = (
            datetime.now() - start_time
        ).total_seconds()

        return result

    async def generate_quality_report(
        self, df: pd.DataFrame, object_name: str
    ) -> DataQualityReport:
        """
        Generate comprehensive data quality report.

        Args:
            df: DataFrame to analyze
            object_name: HubSpot object type

        Returns:
            DataQualityReport with quality metrics
        """
        report = DataQualityReport(
            total_records=len(df), valid_records=0, invalid_records=0
        )

        # Run validation to get issues
        validation_result = await self.validate_dataframe(df, object_name)
        report.invalid_records = len(
            df[
                df.index.isin(
                    [
                        i.row_index
                        for i in validation_result.issues
                        if i.row_index is not None
                    ]
                )
            ]
        )
        report.valid_records = report.total_records - report.invalid_records

        # Calculate field metrics
        for column in df.columns:
            report.field_metrics[column] = {
                "null_count": df[column].isna().sum(),
                "null_percentage": (df[column].isna().sum() / len(df)) * 100,
                "unique_count": df[column].nunique(),
                "unique_percentage": (df[column].nunique() / len(df)) * 100,
                "most_common": df[column].mode().iloc[0]
                if not df[column].empty
                else None,
                "data_type": str(df[column].dtype),
            }

        # Calculate scores
        report.completeness_score = self._calculate_completeness_score(df)
        report.accuracy_score = self._calculate_accuracy_score(validation_result)
        report.consistency_score = self._calculate_consistency_score(df)
        report.overall_score = (
            report.completeness_score * 0.3
            + report.accuracy_score * 0.4
            + report.consistency_score * 0.3
        )

        # Duplicate analysis
        if object_name == "contacts":
            email_dupes = df[df.duplicated(subset=["email"], keep=False)]
            report.duplicate_analysis["email_duplicates"] = len(email_dupes)
        elif object_name == "companies":
            domain_dupes = df[df.duplicated(subset=["domain"], keep=False)]
            report.duplicate_analysis["domain_duplicates"] = len(domain_dupes)

        # Pattern violations
        for issue in validation_result.issues:
            if issue.rule_type == "format":
                pattern_key = f"{issue.field_name}_format_violations"
                report.pattern_violations[pattern_key] = (
                    report.pattern_violations.get(pattern_key, 0) + 1
                )

        return report

    async def _get_properties(self, object_name: str) -> List[HubSpotProperty]:
        """Get cached properties or fetch from API."""
        if object_name not in self._property_cache:
            self._property_cache[
                object_name
            ] = await self.integration.get_object_properties(object_name)
        return self._property_cache[object_name]

    async def _validate_schema(
        self,
        df: pd.DataFrame,
        property_map: Dict[str, HubSpotProperty],
        operation: str,
        result: ValidationResult,
    ):
        """Validate DataFrame columns against HubSpot schema."""
        # Check for unknown columns
        valid_properties = set(property_map.keys())
        df_columns = set(df.columns)

        unknown_columns = df_columns - valid_properties
        if unknown_columns:
            for col in unknown_columns:
                result.add_issue(
                    ValidationIssue(
                        severity="warning",
                        rule_type="schema",
                        field_name=col,
                        message=f"Unknown property '{col}' - will be ignored by HubSpot",
                    )
                )

        # For updates, check if ID field is present
        if operation in ["UPDATE_ONLY", "CREATE_AND_UPDATE"]:
            if "id" not in df.columns and "hs_object_id" not in df.columns:
                result.add_issue(
                    ValidationIssue(
                        severity="error",
                        rule_type="schema",
                        field_name="id",
                        message="ID field required for update operations",
                    )
                )

    async def _validate_required_fields(
        self,
        df: pd.DataFrame,
        property_map: Dict[str, HubSpotProperty],
        operation: str,
        result: ValidationResult,
    ):
        """Check required fields are present and not null."""
        if operation == "UPDATE_ONLY":
            return  # Skip required validation for updates

        for prop_name, prop in property_map.items():
            if prop.required and prop_name in df.columns:
                null_rows = df[df[prop_name].isna()].index
                for row_idx in null_rows:
                    result.add_issue(
                        ValidationIssue(
                            severity="error",
                            rule_type="required",
                            field_name=prop_name,
                            row_index=row_idx,
                            message=f"Required field '{prop_name}' is missing",
                        )
                    )

    async def _validate_field_types(
        self,
        df: pd.DataFrame,
        property_map: Dict[str, HubSpotProperty],
        result: ValidationResult,
    ):
        """Validate field data types."""
        type_validators = {
            "string": self._is_string,
            "number": self._is_number,
            "date": self._is_date,
            "datetime": self._is_datetime,
            "bool": self._is_bool,
            "enumeration": self._is_string,  # Enums are strings
        }

        for prop_name, prop in property_map.items():
            if prop_name not in df.columns:
                continue

            validator = type_validators.get(prop.type)
            if not validator:
                continue

            for idx, value in df[prop_name].items():
                if pd.isna(value):
                    continue

                if not validator(value):
                    result.add_issue(
                        ValidationIssue(
                            severity="error",
                            rule_type="type",
                            field_name=prop_name,
                            row_index=idx,
                            value=value,
                            message=f"Invalid type for '{prop_name}' - expected {prop.type}, got {type(value).__name__}",
                        )
                    )

    async def _validate_field_lengths(
        self,
        df: pd.DataFrame,
        property_map: Dict[str, HubSpotProperty],
        result: ValidationResult,
    ):
        """Validate string field lengths."""
        for prop_name, prop in property_map.items():
            if prop_name not in df.columns or not prop.max_length:
                continue

            for idx, value in df[prop_name].items():
                if pd.isna(value):
                    continue

                str_value = str(value)
                if len(str_value) > prop.max_length:
                    result.add_issue(
                        ValidationIssue(
                            severity="error",
                            rule_type="length",
                            field_name=prop_name,
                            row_index=idx,
                            value=value,
                            message=f"Value too long for '{prop_name}' - max length is {prop.max_length}, got {len(str_value)}",
                            suggested_fix=str_value[: prop.max_length],
                        )
                    )

    async def _validate_field_formats(
        self,
        df: pd.DataFrame,
        property_map: Dict[str, HubSpotProperty],
        result: ValidationResult,
    ):
        """Validate field formats (email, phone, URL, etc.)."""
        format_validators = {
            "email": (self.EMAIL_PATTERN, "Invalid email format"),
            "phone": (self.PHONE_PATTERN, "Invalid phone format"),
            "url": (self.URL_PATTERN, "Invalid URL format"),
            "hubspot_id": (self.HUBSPOT_ID_PATTERN, "Invalid HubSpot ID format"),
        }

        for prop_name, prop in property_map.items():
            if prop_name not in df.columns:
                continue

            # Determine validator based on field name or type
            validator_key = None
            if "email" in prop_name.lower():
                validator_key = "email"
            elif "phone" in prop_name.lower() or "mobile" in prop_name.lower():
                validator_key = "phone"
            elif "url" in prop_name.lower() or "website" in prop_name.lower():
                validator_key = "url"
            elif prop_name.endswith("_id") and "hubspot" in prop_name.lower():
                validator_key = "hubspot_id"

            if not validator_key:
                continue

            pattern, error_msg = format_validators[validator_key]

            for idx, value in df[prop_name].items():
                if pd.isna(value):
                    continue

                str_value = str(value).strip()
                if not pattern.match(str_value):
                    result.add_issue(
                        ValidationIssue(
                            severity="error",
                            rule_type="format",
                            field_name=prop_name,
                            row_index=idx,
                            value=value,
                            message=f"{error_msg} for '{prop_name}'",
                        )
                    )

    async def _validate_enumeration_values(
        self,
        df: pd.DataFrame,
        property_map: Dict[str, HubSpotProperty],
        result: ValidationResult,
    ):
        """Validate enumeration (select/radio) field values."""
        for prop_name, prop in property_map.items():
            if prop_name not in df.columns or prop.type != "enumeration":
                continue

            if not prop.options:
                continue

            valid_values = {opt["value"] for opt in prop.options}

            for idx, value in df[prop_name].items():
                if pd.isna(value):
                    continue

                # Handle multi-checkbox fields (semicolon-separated)
                if prop.field_type == "checkbox":
                    values = str(value).split(";")
                    invalid_values = [
                        v.strip() for v in values if v.strip() not in valid_values
                    ]
                    if invalid_values:
                        result.add_issue(
                            ValidationIssue(
                                severity="error",
                                rule_type="enumeration",
                                field_name=prop_name,
                                row_index=idx,
                                value=value,
                                message=f"Invalid values {invalid_values} for '{prop_name}' - valid options are {valid_values}",
                            )
                        )
                else:
                    if str(value) not in valid_values:
                        result.add_issue(
                            ValidationIssue(
                                severity="error",
                                rule_type="enumeration",
                                field_name=prop_name,
                                row_index=idx,
                                value=value,
                                message=f"Invalid value '{value}' for '{prop_name}' - valid options are {valid_values}",
                            )
                        )

    async def _validate_read_only_fields(
        self,
        df: pd.DataFrame,
        property_map: Dict[str, HubSpotProperty],
        operation: str,
        result: ValidationResult,
    ):
        """Check for attempts to set read-only fields."""
        for prop_name in df.columns:
            # Check if it's a system field by pattern
            is_system_field = (
                any(
                    prop_name.startswith(prefix.replace("*", ""))
                    for prefix in self.READ_ONLY_FIELDS
                    if "*" in prefix
                )
                or prop_name in self.READ_ONLY_FIELDS
            )

            # Check property metadata
            prop = property_map.get(prop_name)
            is_read_only = prop and prop.read_only

            if is_system_field or is_read_only:
                # Check if any values are being set
                non_null_count = df[prop_name].notna().sum()
                if non_null_count > 0:
                    result.add_issue(
                        ValidationIssue(
                            severity="warning"
                            if operation == "UPDATE_ONLY"
                            else "error",
                            rule_type="read_only",
                            field_name=prop_name,
                            message=f"Cannot set read-only field '{prop_name}' - will be ignored",
                        )
                    )

    async def _validate_hubspot_specific_rules(
        self, df: pd.DataFrame, object_name: str, result: ValidationResult
    ):
        """Validate HubSpot-specific business rules."""
        # Contact-specific rules
        if object_name == "contacts":
            # Check email domain matches company domain if both present
            if "email" in df.columns and "company" in df.columns:
                for idx, row in df.iterrows():
                    email = row.get("email")
                    company = row.get("company")
                    if pd.notna(email) and pd.notna(company):
                        email_domain = email.split("@")[-1].lower()
                        # This is a simplified check - in practice you'd look up company domain
                        if company.lower().replace(" ", "") not in email_domain:
                            result.add_issue(
                                ValidationIssue(
                                    severity="warning",
                                    rule_type="business_logic",
                                    field_name="email",
                                    row_index=idx,
                                    value=email,
                                    message=f"Email domain '{email_domain}' doesn't match company '{company}'",
                                )
                            )

        # Deal-specific rules
        elif object_name == "deals":
            # Check deal stage belongs to pipeline
            if "dealstage" in df.columns and "pipeline" in df.columns:
                # This would normally check against actual pipeline stages
                for idx, row in df.iterrows():
                    stage = row.get("dealstage")
                    pipeline = row.get("pipeline")
                    if pd.notna(stage) and pd.notna(pipeline):
                        # Simplified validation
                        if pipeline == "default" and stage not in [
                            "appointmentscheduled",
                            "qualifiedtobuy",
                            "presentationscheduled",
                        ]:
                            result.add_issue(
                                ValidationIssue(
                                    severity="error",
                                    rule_type="business_logic",
                                    field_name="dealstage",
                                    row_index=idx,
                                    value=stage,
                                    message=f"Deal stage '{stage}' not valid for pipeline '{pipeline}'",
                                )
                            )

    async def _validate_duplicates(
        self, df: pd.DataFrame, object_name: str, result: ValidationResult
    ):
        """Check for duplicates within the dataset."""
        duplicate_keys = {
            "contacts": ["email"],
            "companies": ["domain", "name"],
            "deals": ["dealname"],
        }

        key_fields = duplicate_keys.get(object_name, [])

        for key_field in key_fields:
            if key_field not in df.columns:
                continue

            # Find duplicates
            duplicated = df[df.duplicated(subset=[key_field], keep=False)]
            if not duplicated.empty:
                # Group by duplicate value
                for value, group in duplicated.groupby(key_field):
                    if pd.notna(value):
                        for idx in group.index:
                            result.add_issue(
                                ValidationIssue(
                                    severity="warning",
                                    rule_type="duplicate",
                                    field_name=key_field,
                                    row_index=idx,
                                    value=value,
                                    message=f"Duplicate {key_field} '{value}' found in {len(group)} rows",
                                )
                            )

    # Helper methods for type validation
    def _is_string(self, value: Any) -> bool:
        """Check if value can be converted to string."""
        return True  # Everything can be a string

    def _is_number(self, value: Any) -> bool:
        """Check if value is numeric."""
        try:
            float(value)
            return True
        except (ValueError, TypeError):
            return False

    def _is_date(self, value: Any) -> bool:
        """Check if value is a valid date."""
        if isinstance(value, datetime):
            return True
        try:
            # Try parsing common date formats
            pd.to_datetime(value, format="%Y-%m-%d")
            return True
        except:
            return False

    def _is_datetime(self, value: Any) -> bool:
        """Check if value is a valid datetime."""
        if isinstance(value, datetime):
            return True
        try:
            pd.to_datetime(value)
            return True
        except:
            return False

    def _is_bool(self, value: Any) -> bool:
        """Check if value is boolean."""
        return isinstance(value, bool) or value in [
            "true",
            "false",
            "True",
            "False",
            0,
            1,
        ]

    def _calculate_completeness_score(self, df: pd.DataFrame) -> float:
        """Calculate data completeness score (0-1)."""
        total_cells = len(df) * len(df.columns)
        if total_cells == 0:
            return 1.0
        null_cells = df.isna().sum().sum()
        return 1.0 - (null_cells / total_cells)

    def _calculate_accuracy_score(self, validation_result: ValidationResult) -> float:
        """Calculate accuracy score based on validation errors."""
        if validation_result.total_rows == 0:
            return 1.0
        error_rows = len(
            set(
                issue.row_index
                for issue in validation_result.issues
                if issue.severity == "error" and issue.row_index is not None
            )
        )
        return 1.0 - (error_rows / validation_result.total_rows)

    def _calculate_consistency_score(self, df: pd.DataFrame) -> float:
        """Calculate data consistency score."""
        # Simple implementation - check for consistent formatting
        score = 1.0

        # Check email format consistency
        if "email" in df.columns:
            emails = df["email"].dropna()
            if len(emails) > 0:
                # Check if all emails are lowercase
                lowercase_count = sum(1 for email in emails if email == email.lower())
                score *= lowercase_count / len(emails)

        # Add more consistency checks as needed

        return score
