"""
Dynamics 365 Workflow Engine
Handles dedupe, Contact->Account association, and other workflows
FIXED: Correct Merge action schema for v9.2
"""

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

logger = logging.getLogger(__name__)


@dataclass
class WorkflowResult:
    """Result of workflow execution"""

    workflow_name: str
    status: str  # "success", "partial", "failed"
    total_processed: int = 0
    successful: int = 0
    failed: int = 0
    errors: List[Dict[str, Any]] = None
    execution_time: float = 0.0
    details: Dict[str, Any] = None


class DynamicsWorkflowEngine:
    """
    Workflow engine for Dynamics 365
    Subclassed from base WorkflowEngine pattern
    """

    def __init__(self, integration, audit_logger=None):
        """
        Args:
            integration: DynamicsIntegration instance
            audit_logger: Optional AuditLogger instance
        """
        self.integration = integration
        self.audit_logger = audit_logger
        self.batch_size = 500  # Dynamics recommended batch size

    async def execute_workflow(
        self,
        workflow_type: str,
        params: Dict[str, Any],
        progress_cb: Optional[Callable[[float, str], None]] = None,
    ) -> WorkflowResult:
        """
        Execute a workflow based on type

        Args:
            workflow_type: Type of workflow to execute
            params: Workflow-specific parameters
            progress_cb: Progress callback function

        Returns:
            WorkflowResult with execution details
        """
        start_time = datetime.utcnow()

        # Route to specific workflow
        if workflow_type == "entity_dedupe":
            result = await self._execute_dedupe_workflow(params, progress_cb)
        elif workflow_type == "contact_account_association":
            result = await self._execute_contact_account_workflow(params, progress_cb)
        elif workflow_type == "tag_propagation":
            result = await self._execute_tag_propagation_workflow(params, progress_cb)
        elif workflow_type == "bulk_upsert":
            result = await self._execute_bulk_upsert_workflow(params, progress_cb)
        else:
            raise ValueError(f"Unknown workflow type: {workflow_type}")

        # Calculate execution time
        result.execution_time = (datetime.utcnow() - start_time).total_seconds()

        # Log to audit system
        if self.audit_logger:
            await self._log_workflow_execution(result)

        return result

    async def _execute_dedupe_workflow(
        self,
        params: Dict[str, Any],
        progress_cb: Optional[Callable[[float, str], None]] = None,
    ) -> WorkflowResult:
        """
        Execute entity deduplication workflow

        Params:
            entity: Entity type (contact, account, lead)
            duplicate_groups: List of duplicate record groups
            merge_strategy: Strategy for merging (newest, oldest, most_complete)
        """
        entity = params["entity"]
        duplicate_groups = params["duplicate_groups"]
        merge_strategy = params.get("merge_strategy", "most_complete")

        result = WorkflowResult(
            workflow_name="entity_dedupe",
            status="success",
            total_processed=sum(len(group) for group in duplicate_groups),
            errors=[],
        )

        if progress_cb:
            progress_cb(0.0, f"Starting {entity} deduplication...")

        # Process each duplicate group
        for idx, group in enumerate(duplicate_groups):
            if len(group) < 2:
                continue

            try:
                # Determine master record based on strategy
                master_id = await self._select_master_record(
                    entity, group, merge_strategy
                )

                # Merge duplicates into master
                # Dynamics Merge action only supports 2 records at a time
                for duplicate_id in group:
                    if duplicate_id == master_id:
                        continue

                    merge_success = await self._merge_records(
                        entity, master_id, duplicate_id
                    )

                    if merge_success:
                        result.successful += 1
                    else:
                        result.failed += 1
                        result.errors.append(
                            {
                                "master": master_id,
                                "duplicate": duplicate_id,
                                "error": "Merge failed",
                            }
                        )

                if progress_cb:
                    progress = (idx + 1) / len(duplicate_groups)
                    progress_cb(
                        progress,
                        f"Processed {idx + 1} of {len(duplicate_groups)} groups",
                    )

            except Exception as e:
                logger.error(f"Error processing duplicate group: {str(e)}")
                result.failed += len(group)
                result.errors.append({"group": group, "error": str(e)})

        # Update status based on results
        if result.failed > 0:
            result.status = "partial" if result.successful > 0 else "failed"

        if progress_cb:
            progress_cb(1.0, "Deduplication complete")

        return result

    async def _select_master_record(
        self, entity: str, record_ids: List[str], strategy: str
    ) -> str:
        """Select master record based on merge strategy"""
        if strategy == "first":
            return record_ids[0]

        # Get entity set name
        entity_set = self.integration._get_entity_set_name(entity)

        # Fetch record details for intelligent selection
        records = []
        for record_id in record_ids:
            url = f"{self.integration.credentials.resource}/api/data/v9.2/{entity_set}({record_id})"

            async with await self.integration._make_request("GET", url) as resp:
                if resp.status == 200:
                    records.append(await resp.json())

        if not records:
            return record_ids[0]

        # Apply strategy
        if strategy == "newest":
            # Sort by createdon descending
            records.sort(key=lambda x: x.get("createdon", ""), reverse=True)
            return records[0].get(f"{entity}id")

        elif strategy == "oldest":
            # Sort by createdon ascending
            records.sort(key=lambda x: x.get("createdon", ""))
            return records[0].get(f"{entity}id")

        elif strategy == "most_complete":
            # Score based on field completeness
            best_score = -1
            best_record = records[0]

            for record in records:
                score = sum(1 for v in record.values() if v is not None and v != "")
                if score > best_score:
                    best_score = score
                    best_record = record

            return best_record.get(f"{entity}id")

        return record_ids[0]

    async def _merge_records(
        self, entity: str, master_id: str, duplicate_id: str
    ) -> bool:
        """Merge two records using Dynamics Merge action - FIXED v9.2 schema"""
        merge_url = f"{self.integration.credentials.resource}/api/data/v9.2/Merge"

        # Correct v9.2 merge schema
        merge_data = {
            "Target": {
                f"{entity}id": master_id,
                "@odata.type": f"Microsoft.Dynamics.CRM.{entity}",
            },
            "Subordinate": {
                f"{entity}id": duplicate_id,
                "@odata.type": f"Microsoft.Dynamics.CRM.{entity}",
            },
            "UpdateContent": {
                "@odata.type": f"Microsoft.Dynamics.CRM.{entity}"
                # Add specific fields to preserve from subordinate if needed
            },
            "PerformParentingChecks": False,
        }

        try:
            headers = self.integration._get_headers()
            headers["Prefer"] = "return=representation"  # Get merged record back

            async with await self.integration._make_request(
                "POST", merge_url, json=merge_data, headers=headers
            ) as resp:
                if resp.status in [200, 204]:
                    logger.info(f"Successfully merged {duplicate_id} into {master_id}")
                    return True
                else:
                    error_text = await resp.text()
                    logger.error(f"Merge failed ({resp.status}): {error_text}")
                    return False

        except Exception as e:
            logger.error(f"Merge failed: {str(e)}")
            return False

    async def _execute_contact_account_workflow(
        self,
        params: Dict[str, Any],
        progress_cb: Optional[Callable[[float, str], None]] = None,
    ) -> WorkflowResult:
        """
        Execute Contact to Account association workflow

        Params:
            associations: List of (contact_id, account_id) tuples
            relationship_type: Type of relationship (parentcustomerid, accountid)
        """
        associations = params["associations"]
        relationship_type = params.get("relationship_type", "parentcustomerid")

        result = WorkflowResult(
            workflow_name="contact_account_association",
            status="success",
            total_processed=len(associations),
            errors=[],
        )

        if progress_cb:
            progress_cb(0.0, "Starting contact-account associations...")

        # Process in batches
        batches = [
            associations[i : i + self.batch_size]
            for i in range(0, len(associations), self.batch_size)
        ]

        for batch_idx, batch in enumerate(batches):
            batch_requests = []

            for contact_id, account_id in batch:
                # Build association request with proper navigation property
                if relationship_type == "parentcustomerid":
                    # Use navigation property binding for customer lookup
                    request = {
                        "method": "PATCH",
                        "url": f"contacts({contact_id})",
                        "headers": {"Content-Type": "application/json"},
                        "body": {
                            "parentcustomerid_account@odata.bind": f"/accounts({account_id})"
                        },
                    }
                else:
                    # Alternative binding for other relationships
                    request = {
                        "method": "PATCH",
                        "url": f"contacts({contact_id})",
                        "headers": {"Content-Type": "application/json"},
                        "body": {
                            f"{relationship_type}@odata.bind": f"/accounts({account_id})"
                        },
                    }

                batch_requests.append(request)

            # Execute batch
            try:
                batch_results = await self.integration.execute_batch(batch_requests)

                for idx, batch_result in enumerate(batch_results):
                    if batch_result.get("status", 0) in [200, 204]:
                        result.successful += 1
                    else:
                        result.failed += 1
                        result.errors.append(
                            {
                                "contact_id": batch[idx][0],
                                "account_id": batch[idx][1],
                                "error": batch_result.get("error", "Unknown error"),
                            }
                        )

            except Exception as e:
                logger.error(f"Batch execution failed: {str(e)}")
                result.failed += len(batch)
                result.status = "partial"

            if progress_cb:
                progress = (batch_idx + 1) / len(batches)
                progress_cb(
                    progress, f"Processed {batch_idx + 1} of {len(batches)} batches"
                )

        # Update final status
        if result.failed > 0:
            result.status = "partial" if result.successful > 0 else "failed"

        if progress_cb:
            progress_cb(1.0, "Association workflow complete")

        return result

    async def _execute_tag_propagation_workflow(
        self,
        params: Dict[str, Any],
        progress_cb: Optional[Callable[[float, str], None]] = None,
    ) -> WorkflowResult:
        """
        Execute tag propagation workflow (e.g., ICP flag)

        Params:
            entity: Target entity type
            records: List of record IDs to update
            updates: Dict of field updates to apply
        """
        entity = params["entity"]
        records = params["records"]
        updates = params["updates"]

        # Get entity set name
        entity_set = self.integration._get_entity_set_name(entity)

        result = WorkflowResult(
            workflow_name="tag_propagation",
            status="success",
            total_processed=len(records),
            errors=[],
        )

        if progress_cb:
            progress_cb(0.0, f"Starting {entity} tag propagation...")

        # Process in batches
        batches = [
            records[i : i + self.batch_size]
            for i in range(0, len(records), self.batch_size)
        ]

        for batch_idx, batch in enumerate(batches):
            batch_requests = []

            for record_id in batch:
                request = {
                    "method": "PATCH",
                    "url": f"{entity_set}({record_id})",
                    "headers": {"Content-Type": "application/json"},
                    "body": updates,
                }
                batch_requests.append(request)

            # Execute batch
            try:
                batch_results = await self.integration.execute_batch(batch_requests)

                for idx, batch_result in enumerate(batch_results):
                    if batch_result.get("status", 0) in [200, 204]:
                        result.successful += 1
                    else:
                        result.failed += 1
                        result.errors.append(
                            {
                                "record_id": batch[idx],
                                "error": batch_result.get("error", "Unknown error"),
                            }
                        )

            except Exception as e:
                logger.error(f"Batch execution failed: {str(e)}")
                result.failed += len(batch)

            if progress_cb:
                progress = (batch_idx + 1) / len(batches)
                progress_cb(
                    progress, f"Updated {(batch_idx + 1) * self.batch_size} records"
                )

        # Update final status
        if result.failed > 0:
            result.status = "partial" if result.successful > 0 else "failed"

        if progress_cb:
            progress_cb(1.0, "Tag propagation complete")

        return result

    async def _execute_bulk_upsert_workflow(
        self,
        params: Dict[str, Any],
        progress_cb: Optional[Callable[[float, str], None]] = None,
    ) -> WorkflowResult:
        """
        Execute bulk upsert workflow

        Params:
            entity: Target entity
            records: List of records to upsert
            key_field: Field to use for matching
        """
        from .bulk_api import DynamicsBulkAPI

        bulk_api = DynamicsBulkAPI(self.integration)

        entity = params["entity"]
        records = params["records"]
        key_field = params["key_field"]

        # Execute bulk upsert
        bulk_result = await bulk_api.execute_bulk_upsert(
            entity=entity, records=records, key_field=key_field, progress_cb=progress_cb
        )

        # Convert to WorkflowResult
        result = WorkflowResult(
            workflow_name="bulk_upsert",
            status="success" if bulk_result["failed"] == 0 else "partial",
            total_processed=bulk_result["total"],
            successful=bulk_result["succeeded"],
            failed=bulk_result["failed"],
            errors=bulk_result["errors"],
            details=bulk_result,
        )

        return result

    async def _log_workflow_execution(self, result: WorkflowResult):
        """Log workflow execution to audit system"""
        if not self.audit_logger:
            return

        audit_data = {
            "workflow_name": result.workflow_name,
            "status": result.status,
            "total_processed": result.total_processed,
            "successful": result.successful,
            "failed": result.failed,
            "execution_time": result.execution_time,
            "error_count": len(result.errors) if result.errors else 0,
            "integration": "dynamics365",
        }

        await self.audit_logger.log_workflow_execution(audit_data)

    async def execute_field_mapping_workflow(
        self,
        source_entity: str,
        target_entity: str,
        field_mappings: Dict[str, str],
        filter_criteria: Optional[str] = None,
        progress_cb: Optional[Callable[[float, str], None]] = None,
    ) -> WorkflowResult:
        """
        Execute field mapping workflow between entities

        Args:
            source_entity: Source entity to read from
            target_entity: Target entity to write to
            field_mappings: Dict mapping source fields to target fields
            filter_criteria: Optional OData filter for source records
            progress_cb: Progress callback

        Returns:
            WorkflowResult with execution details
        """
        result = WorkflowResult(
            workflow_name="field_mapping",
            status="success",
            total_processed=0,
            errors=[],
        )

        if progress_cb:
            progress_cb(
                0.0,
                f"Starting field mapping from {source_entity} to {target_entity}...",
            )

        # Get entity set names
        source_set = self.integration._get_entity_set_name(source_entity)
        target_set = self.integration._get_entity_set_name(target_entity)

        # Build query for source records
        select_fields = list(field_mappings.keys()) + [f"{source_entity}id"]
        query_url = (
            f"{self.integration.credentials.resource}/api/data/v9.2/{source_set}"
        )
        params = {"$select": ",".join(select_fields)}

        if filter_criteria:
            params["$filter"] = filter_criteria

        # Fetch source records
        source_records = []

        async with await self.integration._make_request(
            "GET", query_url, params=params
        ) as resp:
            if resp.status == 200:
                data = await resp.json()
                source_records = data.get("value", [])

                # Handle paging if needed
                while "@odata.nextLink" in data:
                    next_url = data["@odata.nextLink"]
                    async with await self.integration._make_request(
                        "GET", next_url
                    ) as next_resp:
                        if next_resp.status == 200:
                            data = await next_resp.json()
                            source_records.extend(data.get("value", []))
                        else:
                            break

        result.total_processed = len(source_records)

        if progress_cb:
            progress_cb(0.2, f"Fetched {len(source_records)} source records")

        # Process in batches
        batches = [
            source_records[i : i + self.batch_size]
            for i in range(0, len(source_records), self.batch_size)
        ]

        for batch_idx, batch in enumerate(batches):
            batch_requests = []

            for record in batch:
                # Map fields
                target_data = {}
                for source_field, target_field in field_mappings.items():
                    if source_field in record:
                        target_data[target_field] = record[source_field]

                # Create target record
                request = {
                    "method": "POST",
                    "url": target_set,
                    "headers": {"Content-Type": "application/json"},
                    "body": target_data,
                }
                batch_requests.append(request)

            # Execute batch
            try:
                batch_results = await self.integration.execute_batch(batch_requests)

                for idx, batch_result in enumerate(batch_results):
                    if batch_result.get("status", 0) in [200, 201, 204]:
                        result.successful += 1
                    else:
                        result.failed += 1
                        result.errors.append(
                            {
                                "source_record_id": batch[idx].get(
                                    f"{source_entity}id"
                                ),
                                "error": batch_result.get("error", "Unknown error"),
                            }
                        )

            except Exception as e:
                logger.error(f"Batch execution failed: {str(e)}")
                result.failed += len(batch)

            if progress_cb:
                progress = 0.2 + (0.8 * (batch_idx + 1) / len(batches))
                progress_cb(
                    progress, f"Processed {(batch_idx + 1) * self.batch_size} mappings"
                )

        # Update final status
        if result.failed > 0:
            result.status = "partial" if result.successful > 0 else "failed"

        if progress_cb:
            progress_cb(1.0, "Field mapping complete")

        return result
