"""
Data Models for UDA Acquisition Pipeline
Contains all dataclass definitions for YAML configuration and database entities
"""

from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Optional, List, Dict, Any
import hashlib
import json
import re

from mashumaro.mixins.yaml import DataClassYAMLMixin
from mashumaro.config import BaseConfig

# ============================================================================
# ENUMS
# ============================================================================


class SourceType(str, Enum):
    """Source connection types"""

    FTP = "FTP"
    SFTP = "SFTP"
    S3 = "S3"
    GCS = "GCS"


class SinkType(str, Enum):
    """Sink storage types"""

    GCS = "GCS"


class PatternType(str, Enum):
    """File detection pattern types"""

    INCLUDE = "INCLUDE"
    EXCLUDE = "EXCLUDE"


class RunStatus(str, Enum):
    """Download batch run status"""

    PENDING = "PENDING"
    RUNNING = "RUNNING"
    SUCCESS = "SUCCESS"
    FAILED = "FAILED"


class DeployMode(str, Enum):
    """Deployment mode for scanners"""

    CONTINUOUS = "CONTINUOUS"
    SCHEDULED = "SCHEDULED"


# ============================================================================
# VALIDATION HELPER FUNCTIONS
# ============================================================================


def validate_cron_expression(cron_expr: str) -> bool:
    """Validate 5-part cron expression"""
    parts = cron_expr.strip().split()
    if len(parts) != 5:
        return False

    cron_pattern = r"^[\d\*\-\,\/]+$"
    for part in parts:
        if not re.match(cron_pattern, part):
            return False

    return True


# ============================================================================
# YAML CONFIGURATION MODELS
# ============================================================================


@dataclass
class ValidationConfig(DataClassYAMLMixin):
    """Validation configuration for file patterns"""

    folder: str
    pattern: str
    file_count: Optional[Dict[str, float]] = None
    size: Optional[Dict[str, float]] = None


@dataclass
class DetectPattern(DataClassYAMLMixin):
    """File detection pattern (INCLUDE/EXCLUDE)"""

    type: PatternType
    folder_pattern: str
    file_pattern: str
    vendor_date_regex: Optional[str] = None
    validations: Optional[List[ValidationConfig]] = None


@dataclass
class SourceConfig(DataClassYAMLMixin):
    """Source connection configuration"""

    type: SourceType
    key: str


@dataclass
class DeployConfig(DataClassYAMLMixin):
    """Deployment configuration for scanner scheduling"""

    mode: DeployMode
    frequency: str

    def __post_init__(self):
        """Validate frequency based on mode"""
        if self.mode == DeployMode.CONTINUOUS:
            if self.frequency not in ["@Once"]:
                raise ValueError(
                    f"CONTINUOUS mode requires frequency='@Once', got '{self.frequency}'"
                )
        elif self.mode == DeployMode.SCHEDULED:
            if not validate_cron_expression(self.frequency):
                raise ValueError(
                    f"SCHEDULED mode requires valid 5-part cron expression, "
                    f"got '{self.frequency}'"
                )


@dataclass
class SinkConfig(DataClassYAMLMixin):
    """Sink storage configuration"""

    type: SinkType
    bucket_name: str
    prefix_path: Optional[str] = None
    retention_policy: Optional[Dict[str, Any]] = None


@dataclass
class SpecConfig(DataClassYAMLMixin):
    """Specification configuration containing all scanner settings"""

    deploy: DeployConfig
    source: SourceConfig
    detect: List[DetectPattern] = field(default_factory=list)
    sink: SinkConfig = None


@dataclass
class MetadataConfig(DataClassYAMLMixin):
    """Metadata configuration for scanner identification"""

    uda_id: str
    uda_shard_id: int
    org_id: str


@dataclass
class AcquisitionConfig(DataClassYAMLMixin):
    """Top-level acquisition configuration"""

    version: str
    kind: str
    metadata: MetadataConfig
    spec: SpecConfig

    class Config(BaseConfig):
        serialize_by_alias = True


# ============================================================================
# DATABASE MODELS
# ============================================================================


@dataclass
class Downloader:
    """Database model for downloader/scanner"""

    id: str
    key: str  # Changed from downloader_key
    shard_num: int
    source_type: str
    supplier_org_id: Optional[str]
    created_at: datetime
    status: bool
    deploy_mode: Optional[str]
    frequency: Optional[str]
    secret_key: Optional[str]  # Changed from secrets_key
    sink_bucket: Optional[str]
    sink_prefix: Optional[str]
    retention_policy: Optional[Dict[str, Any]]  # Changed from str to Dict (JSONB)

    @staticmethod
    def generate_id(downloader_key: str, shard_num: int) -> str:
        """Generate hash ID from downloader_key and shard_num"""
        data = f"{downloader_key}:{shard_num}"
        return hashlib.sha256(data.encode()).hexdigest()[:32]

    @staticmethod
    def from_config(config: AcquisitionConfig) -> "Downloader":
        """Create Downloader instance from AcquisitionConfig"""
        downloader_key = config.metadata.uda_id
        shard_num = config.metadata.uda_shard_id

        return Downloader(
            id=Downloader.generate_id(downloader_key, shard_num),
            key=downloader_key,  # Changed from downloader_key
            shard_num=shard_num,
            source_type=config.spec.source.type.value,
            supplier_org_id=config.metadata.org_id,
            created_at=datetime.utcnow(),
            status=True,
            deploy_mode=config.spec.deploy.mode.value,
            frequency=config.spec.deploy.frequency,
            secret_key=config.spec.source.key,  # Changed from secrets_key
            sink_bucket=config.spec.sink.bucket_name,
            sink_prefix=config.spec.sink.prefix_path,
            retention_policy=config.spec.sink.retention_policy,
        )


@dataclass
class DownloadPattern:
    """Database model for download pattern"""

    id: str
    downloader_id: str
    created_at: datetime
    type: str
    folder_path: str  # Changed from folder_path_pattern
    file_pattern: str
    vendor_date_regex: Optional[str]

    @staticmethod
    def generate_id(
        downloader_id: str,
        folder_path: str,  # Changed parameter name
        file_pattern: str,
        pattern_type: str,
    ) -> str:
        """Generate hash ID from pattern components"""
        data = f"{downloader_id}:{folder_path}:{file_pattern}:{pattern_type}"
        return hashlib.sha256(data.encode()).hexdigest()[:32]


@dataclass
class ValidationRuleDB:
    """Database model for validation rule"""

    id: str
    download_pattern_id: str
    created_at: datetime
    validation_config: Dict[str, Any]  # Changed from str to Dict (JSONB)

    @staticmethod
    def generate_id(download_pattern_id: str, validation_config: Dict[str, Any]) -> str:
        """Generate hash ID from pattern ID and config"""
        config_str = json.dumps(validation_config, sort_keys=True)
        data = f"{download_pattern_id}:{config_str}"
        return hashlib.sha256(data.encode()).hexdigest()[:32]


@dataclass
class DownloaderRun:
    """Database model for downloader run (renamed from DownloadBatchRun)"""

    id: str  # Changed from int to str
    downloader_id: str
    created_at: datetime
    scan_started_at: datetime
    scan_ended_at: datetime
    files_detected: int
    files_downloaded: int
    status: Optional[str]
    summary: Optional[str]  # Changed from run_summary, limited to 255 chars

    @staticmethod
    def generate_id() -> str:
        """Generate ID from current timestamp in milliseconds"""
        timestamp_ms = int(datetime.utcnow().timestamp() * 1000)
        return str(timestamp_ms)

    @staticmethod
    def generate_run_id(prefix: str = "scheduled") -> str:
        """Generate run_id in format: prefix__YYYY-MM-DDTHH:MM:SS+00:00"""
        timestamp = datetime.utcnow().isoformat() + "+00:00"
        return f"{prefix}__{timestamp}"


@dataclass
class DownloadBatchDetails:
    """Database model for download batch details"""

    id: str
    downloader_run_id: (
        str  # Changed from download_batch_run_id and type from int to str
    )
    created_at: datetime
    file_name: str
    vendor_modified_timestamp: Optional[datetime]
    file_size: Optional[int]
    download_timestamp: Optional[datetime]
    data_lake_path: Optional[str]  # Changed to Optional
