"""
Record enrichment utilities for MongoDB aggregation pipelines.
Provides pipeline builders for associations, users, file URLs, and option labels.

Usage pattern (matches entity_records_repository):
    1. Fetch fields from field_repository
    2. Pass fields to build_lookup_pipeline() to get $lookup stages
    3. Add stages to find.reference_fields
    4. Call repository.get_all(find, collection_name)
    5. Call process_file_fields() on the response to add file URLs
"""
from typing import List, Dict, Any, Optional, Protocol, Literal
from datetime import timedelta
from pydantic import BaseModel, Field
import logging

logger = logging.getLogger(__name__)


# ============================================================================
# Schemas - Define expected field configurations (NOT records)
# ============================================================================

class FieldReference(BaseModel):
    """
    Reference configuration for association fields.
    This comes from field.reference in the fields collection.
    """
    entity_id: str = Field(..., description="ID of the referenced entity")
    fields: Optional[List[str]] = Field(None, description="Fields to project from referenced entity")
    filters: Optional[Dict[str, Any]] = Field(None, description="Filters for the reference")


class FieldConfig(BaseModel):
    """
    Field configuration from fields collection.
    Pass these to build_lookup_pipeline().

    Example - get fields from repository:
        filters = ListFilter(
            pre_filters={
                "operator": "and",
                "conditions": [
                    {"field": "entity_id", "operator": "eq", "value": entity_id},
                    {"field": "type", "operator": "in", "value": ["association", "user"]}
                ]
            }
        )
        fields_result = await field_repository.get_all(filters)
        fields = fields_result.get("founds", [])

        # Then pass to build_lookup_pipeline
        pipeline = enricher.build_lookup_pipeline(fields)
    """
    id: str = Field(..., alias="_id", description="Field ID")
    type: Literal["association", "user", "file", "signature", "select", "multi_select", "text", "number"] = Field(
        ..., description="Field type"
    )
    reference: Optional[FieldReference] = Field(None, description="Reference config for association fields")
    user_filter: Optional[Dict[str, Any]] = Field(None, description="Filter for user fields")

    class Config:
        populate_by_name = True


class OptionConfig(BaseModel):
    """
    Option configuration for select/multi_select fields.

    Example:
        {"value": "uuid-1", "label": "Active"}
    """
    value: str
    label: str


class EnrichmentConfig(BaseModel):
    """
    Complete enrichment configuration returned by prepare_enrichment().

    Contains everything needed to query and enrich records.
    """
    pipeline: List[Dict[str, Any]] = Field(
        default_factory=list,
        description="Combined $lookup and $addFields aggregation stages"
    )
    pre_filter: Optional[Dict[str, Any]] = Field(
        None,
        description="Pre-filter for record IDs (use with find.pre_filters)"
    )
    file_field_ids: List[str] = Field(
        default_factory=list,
        description="Field IDs for file URL processing (use with process_file_fields)"
    )

    class Config:
        arbitrary_types_allowed = True


# ============================================================================
# Protocols
# ============================================================================

class BlobStorageProtocol(Protocol):
    """Protocol for blob storage service (matches EntityRecordBlobRepository)"""
    async def get_file_url(self, file_id: str, container: Optional[str], expiry: timedelta) -> Optional[str]:
        ...


class BlobRepositoryProtocol(Protocol):
    """Protocol for full blob repository (matches BlobStorageRepository from abs_blob_storage_manager_core)"""
    async def upload_file(self, file: Any, request_data: Any) -> Dict[str, Any]:
        ...

    async def delete_file(self, file_id: str) -> bool:
        ...

    async def get_file_url(self, file_id: str, container: Optional[str]) -> str:
        ...

    async def delete_folder(self, path: str, is_public: bool) -> None:
        ...


class RecordEnricher:
    """
    Builds MongoDB aggregation pipelines for record enrichment.
    Matches entity_records_repository.py logic.

    Usage:
        enricher = RecordEnricher(
            blob_service=entityRecordBlobRepository,
            user_collection_name=user_repository.collection_name
        )

        # 1. Fetch association/user fields from field_repository
        fields = await field_repository.get_all(filters)

        # 2. Build lookup pipeline
        lookup_pipeline = enricher.build_lookup_pipeline(fields.get("founds", []))

        # 3. Add to aggregation
        find.reference_fields = AggregationStage(pipeline=lookup_pipeline)

        # 4. Get records
        response = await repository.get_all(find, collection_name)

        # 5. Process file fields (add URLs)
        response = await enricher.process_file_fields(file_field_ids, response)
    """

    def __init__(
        self,
        blob_service: Optional[BlobStorageProtocol] = None,
        user_collection_name: str = "user_documents"
    ):
        """
        Args:
            blob_service: Blob repository for file URL generation (optional)
            user_collection_name: Collection name for user lookups (default: "user_documents")
        """
        self.blob_service = blob_service
        self.user_collection_name = user_collection_name

    def prepare_enrichment(
        self,
        fields: List[Dict[str, Any]],
        record_ids: Optional[List[str]] = None,
        option_field_ids: Optional[List[str]] = None
    ) -> EnrichmentConfig:
        """
        Prepare complete enrichment configuration in one call.
        Combines lookup pipeline, option label pipeline, and record filter.

        Args:
            fields: All field configurations from field_repository.get_all()
            record_ids: Optional list of record IDs to filter by
            option_field_ids: Optional list of field IDs to build option labels for
                             (only for fields in sort/search - matches entity manager pattern)

        Returns:
            EnrichmentConfig with:
                - pipeline: Combined $lookup and $addFields stages
                - pre_filter: Filter schema for record_ids (if provided)
                - file_field_ids: List of file/signature field IDs

        Example:
            # Fetch ALL fields for the entity
            fields_result = await field_repository.get_all(ListFilter(
                pre_filters={
                    "operator": "and",
                    "conditions": [
                        {"field": "entity_id", "operator": "eq", "value": entity_id}
                    ]
                }
            ))
            fields = fields_result.get("founds", [])

            # Prepare enrichment (filter by specific record IDs)
            config = enricher.prepare_enrichment(
                fields=fields,
                record_ids=["rec_id_1", "rec_id_2"],
                option_field_ids=["status_field_id"]  # Only for fields in sort/search
            )

            # Apply to query
            find = ListFilter()
            if config.pre_filter:
                find.pre_filters = config.pre_filter
            find.reference_fields = AggregationStage(pipeline=config.pipeline)

            # Get records
            response = await repository.get_all(find, collection_name)

            # Add file URLs
            response = await enricher.process_file_fields(config.file_field_ids, response)
        """
        pipeline = []
        file_field_ids = []

        # Build lookup pipelines for association/user fields
        for field in fields:
            field_type = field.get("type")
            field_id = str(field.get("_id"))

            if field_type == "association" and field.get("reference"):
                ref = field.get("reference", {})
                ref_entity_id = ref.get("entity_id")
                collection_name = f"entity_{ref_entity_id}"

                lookup_pipeline = []
                if ref.get("fields"):
                    lookup_pipeline.append({
                        "$project": {str(f): 1 for f in ref.get("fields")}
                    })

                pipeline.append({
                    "$lookup": {
                        "from": collection_name,
                        "localField": field_id,
                        "foreignField": "_id",
                        "as": field_id,
                        "pipeline": lookup_pipeline
                    }
                })

            elif field_type == "user":
                pipeline.append({
                    "$lookup": {
                        "from": self.user_collection_name,
                        "localField": field_id,
                        "foreignField": "_id",
                        "as": field_id
                    }
                })

            elif field_type in ("file", "signature"):
                file_field_ids.append(field_id)

            # Build option label pipeline for select/multi_select fields
            # Only if field_id is in option_field_ids (for sort/search fields)
            elif field_type in ("select", "multi_select", "dropdown", "checkbox", "radio"):
                if option_field_ids and field_id in option_field_ids:
                    options = field.get("options", [])
                    if options:
                        value_to_label = {
                            opt.get("value"): opt.get("label")
                            for opt in options
                            if opt.get("value") and opt.get("label")
                        }
                        is_multi_select = field.get("multi_select", False) or field_type == "multi_select"
                        option_stages = self.build_option_label_pipeline(
                            field_id, value_to_label, is_multi_select
                        )
                        pipeline.extend(option_stages)

        # Build pre_filter for record IDs
        pre_filter = None
        if record_ids:
            pre_filter = {
                "operator": "and",
                "conditions": [
                    {"field": "_id", "operator": "in", "value": record_ids}
                ]
            }

        return EnrichmentConfig(
            pipeline=pipeline,
            pre_filter=pre_filter,
            file_field_ids=file_field_ids
        )

    def build_lookup_pipeline(self, fields: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """
        Build $lookup aggregation stages for association and user fields.
        Matches entity_records_repository.get_reference_fields()

        Args:
            fields: List of field documents from field_repository.get_all()
                    Must have: _id, type, reference (for associations)

        Returns:
            List of $lookup stages to add to find.reference_fields

        Example:
            # Fetch fields
            filters = ListFilter(
                pre_filters={
                    "operator": "and",
                    "conditions": [
                        {"field": "entity_id", "operator": "eq", "value": PydanticObjectId(entity_id)},
                        {"field": "type", "operator": "in", "value": ["association", "user"]}
                    ]
                }
            )
            fields_result = await field_repository.get_all(filters)
            fields = fields_result.get("founds", [])

            # Build pipeline
            pipeline = enricher.build_lookup_pipeline(fields)

            # Use in query
            find.reference_fields = AggregationStage(pipeline=pipeline)
            records = await repository.get_all(find, collection_name)
        """
        pipeline = []

        for field in fields:
            field_type = field.get("type")
            field_id = str(field.get("_id"))

            if field_type == "association" and field.get("reference"):
                ref = field.get("reference", {})
                ref_entity_id = ref.get("entity_id")
                collection_name = f"entity_{ref_entity_id}"

                lookup_pipeline = []
                if ref.get("fields"):
                    lookup_pipeline.append({
                        "$project": {str(f): 1 for f in ref.get("fields")}
                    })

                pipeline.append({
                    "$lookup": {
                        "from": collection_name,
                        "localField": field_id,
                        "foreignField": "_id",
                        "as": field_id,
                        "pipeline": lookup_pipeline
                    }
                })

            elif field_type == "user":
                pipeline.append({
                    "$lookup": {
                        "from": self.user_collection_name,
                        "localField": field_id,
                        "foreignField": "_id",
                        "as": field_id
                    }
                })

        return pipeline

    async def process_file_fields(
        self,
        file_fields: List[str],
        response: Dict[str, Any],
        expiry_minutes: int = 5
    ) -> Dict[str, Any]:
        """
        Attach file URLs to file/signature fields in response from get_all().
        Matches entity_records_repository.process_file_fields()

        Args:
            file_fields: List of field IDs that are file/signature type
            response: Response from repository.get_all() with "founds" list or single record
            expiry_minutes: URL expiry time in minutes (default: 5)

        Returns:
            Response with file_url attached to file records

        Example:
            # Get file field IDs
            file_field_ids = [str(f["_id"]) for f in fields if f["type"] in ["file", "signature"]]

            # After getting records
            response = await repository.get_all(find, collection_name)

            # Add file URLs
            response = await enricher.process_file_fields(file_field_ids, response)
        """
        if not self.blob_service:
            return response

        if response.get("founds"):
            records = response.get("founds", []) or []
        else:
            records = [response]

        expiry = timedelta(minutes=expiry_minutes)

        for record in records:
            for field_id in file_fields:
                if field_id not in record:
                    continue

                file_records = record.get(field_id, []) or []
                if not isinstance(file_records, list):
                    continue

                for file_record in file_records:
                    if not isinstance(file_record, dict):
                        continue

                    file_id = file_record.get("file_id")
                    if not file_id:
                        continue

                    try:
                        file_url = await self.blob_service.get_file_url(file_id, None, expiry)
                        file_record["file_url"] = file_url
                    except Exception as e:
                        logger.warning(f"File not found or error for file_id {file_id}: {e}")
                        file_record["file_url"] = None

        if response.get("founds"):
            response["founds"] = records
        else:
            response = records[0] if records else response

        return response

    def build_option_label_pipeline(
        self,
        field_id: str,
        value_to_label: Dict[str, str],
        is_multi_select: bool = False
    ) -> List[Dict[str, Any]]:
        """
        Build $addFields aggregation stage for option label resolution.
        Matches option_field_helper.build_option_field_lookup_pipeline()

        Args:
            field_id: The field ID
            value_to_label: Mapping of option values to labels
                           e.g., {"uuid-1": "Active", "uuid-2": "Inactive"}
            is_multi_select: Whether field allows multiple selections

        Returns:
            List with single $addFields stage that creates {field_id}_label

        Example:
            # Get option mappings for a field
            options = field.get("options", [])
            value_to_label = {opt["value"]: opt["label"] for opt in options}

            # Build pipeline
            pipeline = enricher.build_option_label_pipeline(
                field_id=str(field["_id"]),
                value_to_label=value_to_label,
                is_multi_select=field.get("multi_select", False)
            )

            # Combine with other pipelines
            all_pipelines = lookup_pipeline + option_pipeline
            find.reference_fields = AggregationStage(pipeline=all_pipelines)
        """
        if not value_to_label:
            return []

        switch_branches = [
            {"case": {"$eq": [f"${field_id}", value]}, "then": label}
            for value, label in value_to_label.items()
        ]

        array_map_branches = [
            {"case": {"$eq": ["$$item", value]}, "then": label}
            for value, label in value_to_label.items()
        ]

        label_field = f"{field_id}_label"

        return [{
            "$addFields": {
                label_field: {
                    "$switch": {
                        "branches": [
                            {
                                "case": {"$or": [
                                    {"$eq": [f"${field_id}", None]},
                                    {"$not": [{"$ifNull": [f"${field_id}", False]}]}
                                ]},
                                "then": None
                            },
                            {
                                "case": {"$eq": [{"$type": f"${field_id}"}, "array"]},
                                "then": {
                                    "$let": {
                                        "vars": {"values": {"$ifNull": [f"${field_id}", []]}},
                                        "in": {
                                            "$cond": {
                                                "if": {
                                                    "$and": [
                                                        {"$eq": [{"$size": "$$values"}, 1]},
                                                        {"$eq": [is_multi_select, False]}
                                                    ]
                                                },
                                                "then": {
                                                    "$let": {
                                                        "vars": {"firstValue": {"$arrayElemAt": ["$$values", 0]}},
                                                        "in": {
                                                            "$switch": {
                                                                "branches": [
                                                                    {"case": {"$eq": ["$$firstValue", v]}, "then": l}
                                                                    for v, l in value_to_label.items()
                                                                ],
                                                                "default": "$$firstValue"
                                                            }
                                                        }
                                                    }
                                                },
                                                "else": {
                                                    "$map": {
                                                        "input": "$$values",
                                                        "as": "item",
                                                        "in": {
                                                            "$switch": {
                                                                "branches": array_map_branches,
                                                                "default": "$$item"
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            {
                                "case": {"$eq": [{"$type": f"${field_id}"}, "string"]},
                                "then": {
                                    "$switch": {
                                        "branches": switch_branches,
                                        "default": f"${field_id}"
                                    }
                                }
                            }
                        ],
                        "default": None
                    }
                }
            }
        }]


# ============================================================================
# File Upload/Download Handler
# ============================================================================

class RequestData:
    """
    Request data for blob storage uploads.
    Matches abs_blob_storage_manager_core.schema.blob_storage_schema.RequestData
    """
    def __init__(
        self,
        storage_path: str,
        is_public: bool = False,
        custom_filename: Optional[str] = None,
        timestamp_required: bool = False,
        user_folder: bool = False
    ):
        self.storage_path = storage_path
        self.is_public = is_public
        self.custom_filename = custom_filename
        self.timestamp_required = timestamp_required
        self.user_folder = user_folder


class EntityRecordBlobHandler:
    """
    File upload/download operations for entity records.
    Matches EntityRecordBlobRepository pattern from entity-manager-service.

    Usage:
        from abs_pipelines_core import EntityRecordBlobHandler

        blob_handler = EntityRecordBlobHandler(blob_repository)

        # Upload single file
        result = await blob_handler.upload_record_field_file(entity_id, field_id, file)

        # Upload multiple files (filename format: {field_id}_actualfilename)
        results = await blob_handler.upload_record_field_files(entity_id, files)

        # Get file URL
        url = await blob_handler.get_record_field_file(file_id)

        # Get multiple file URLs
        urls = await blob_handler.get_record_field_files(entity_id, file_ids)

        # Delete file
        await blob_handler.delete_record_field_file(file_id)

        # Delete folder
        await blob_handler.delete_record_field_folder(entity_id, field_id)
    """

    def __init__(self, blob_repository: BlobRepositoryProtocol):
        """
        Args:
            blob_repository: BlobStorageRepository instance from abs_blob_storage_manager_core
        """
        self.blob_repository = blob_repository

    async def upload_record_field_file(
        self,
        entity_id: str,
        field_id: str,
        file: Any
    ) -> Dict[str, Any]:
        """
        Upload single file for a record field.
        Path: entity-{entity_id}/field-{field_id}

        Args:
            entity_id: Entity ID
            field_id: Field ID
            file: UploadFile from FastAPI

        Returns:
            {field_id: file_id} - Dict mapping field_id to uploaded file ID
        """
        storage_path = f"entity-{entity_id}/field-{field_id}"
        custom_filename = getattr(file, 'filename', None)
        request_data = RequestData(
            storage_path=storage_path,
            is_public=False,
            custom_filename=custom_filename,
            timestamp_required=False,
            user_folder=False
        )
        uploaded_file_data = await self.blob_repository.upload_file(file, request_data)
        return {field_id: uploaded_file_data.get("id")}

    async def upload_record_field_files(
        self,
        entity_id: str,
        files: List[Any]
    ) -> List[Dict[str, Any]]:
        """
        Upload multiple files in parallel.
        Expects filename format: {field_id}_actualfilename

        Args:
            entity_id: Entity ID
            files: List of UploadFile from FastAPI

        Returns:
            List of {field_id: file_id} dicts

        Raises:
            ValueError: If no files were successfully uploaded
        """
        import asyncio

        async def upload_single(file, field_id: str, filename: str):
            try:
                request_data = RequestData(
                    storage_path=f"entity-{entity_id}/field-{field_id}",
                    is_public=False,
                    custom_filename=filename,
                    timestamp_required=True,
                    user_folder=False
                )
                return await self.blob_repository.upload_file(file, request_data)
            except Exception as e:
                logger.error(f"Failed to upload file {getattr(file, 'filename', 'unknown')}: {e}")
                return e

        tasks = []
        field_ids = []
        for file in files:
            filename = getattr(file, 'filename', '') or ''
            parts = filename.split("_", 1)
            field_id = parts[0] if len(parts) > 1 else ''
            actual_filename = parts[1] if len(parts) > 1 else filename
            field_ids.append(field_id)
            tasks.append(upload_single(file, field_id, actual_filename))

        results = await asyncio.gather(*tasks, return_exceptions=True)

        files_data = []
        for i, result in enumerate(results):
            if isinstance(result, Exception):
                logger.error(f"Error uploading file {getattr(files[i], 'filename', 'unknown')}: {result}")
            else:
                files_data.append({field_ids[i]: result.get("id")})

        if not files_data:
            raise ValueError("No files were successfully uploaded")

        return files_data

    async def delete_record_field_file(self, file_id: str) -> bool:
        """
        Delete single file by ID.

        Args:
            file_id: File ID to delete

        Returns:
            True on success

        Raises:
            Exception on failure
        """
        try:
            await self.blob_repository.delete_file(file_id)
            return True
        except Exception as e:
            logger.error(f"Failed to delete file {file_id}: {e}")
            raise

    async def get_record_field_file(self, file_id: str) -> str:
        """
        Get accessible URL for a single file.

        Args:
            file_id: File ID

        Returns:
            File URL string
        """
        return await self.blob_repository.get_file_url(file_id, None)

    async def get_record_field_files(
        self,
        entity_id: str,
        file_ids: List[str]
    ) -> List[Dict[str, str]]:
        """
        Get URLs for multiple files.

        Args:
            entity_id: Entity ID (for context, not used in lookup)
            file_ids: List of file IDs

        Returns:
            List of {file_id: url} dicts (skips failed retrievals)
        """
        file_urls = []
        for file_id in file_ids:
            try:
                file_url = await self.blob_repository.get_file_url(file_id, None)
                file_urls.append({file_id: file_url})
            except Exception as e:
                logger.error(f"Failed to get file url {file_id}: {e}")
                continue
        return file_urls

    async def delete_record_field_folder(
        self,
        entity_id: str,
        field_id: str,
        is_public: bool = False
    ) -> None:
        """
        Delete entire folder for a field.

        Args:
            entity_id: Entity ID
            field_id: Field ID
            is_public: Whether to delete from public container
        """
        await self.blob_repository.delete_folder(
            f"entity-{entity_id}/field-{field_id}",
            is_public=is_public
        )
