"""
Orchestrator Scheduler Service - REFACTORED
Coordinates daily auto-generation workflow: Forecasting → Production → Procurement

CHANGES FROM ORIGINAL:
- Updated to use new EventPublisher pattern for all messaging
- Integrated OrchestrationSaga for error handling and compensation
- Added circuit breakers for all service calls
- Implemented real Forecasting Service integration
- Implemented real Production Service integration
- Implemented real Tenant Service integration
- Implemented real Notification Service integration
- NO backwards compatibility, NO feature flags - complete rewrite
"""

import asyncio
import uuid
from datetime import datetime, date, timezone
from decimal import Decimal
from typing import List, Dict, Any, Optional
import structlog
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger

# Updated imports - removed old alert system
from shared.messaging import UnifiedEventPublisher
from shared.clients.forecast_client import ForecastServiceClient
from shared.clients.production_client import ProductionServiceClient
from shared.clients.procurement_client import ProcurementServiceClient
from shared.clients.notification_client import NotificationServiceClient
from shared.clients.tenant_client import TenantServiceClient
from shared.clients.inventory_client import InventoryServiceClient
from shared.clients.suppliers_client import SuppliersServiceClient
from shared.clients.recipes_client import RecipesServiceClient
from shared.clients.training_client import TrainingServiceClient
from shared.utils.circuit_breaker import CircuitBreaker, CircuitBreakerOpenError
from app.core.config import settings
from app.repositories.orchestration_run_repository import OrchestrationRunRepository
from app.models.orchestration_run import OrchestrationStatus
from app.services.orchestration_saga import OrchestrationSaga

logger = structlog.get_logger()


class OrchestratorSchedulerService:
    """
    Orchestrator Service using EventPublisher for messaging
    Handles automated daily orchestration of forecasting, production, and procurement
    """

    def __init__(self, event_publisher: UnifiedEventPublisher, config):
        self.publisher = event_publisher
        self.config = config

        # APScheduler instance for running daily orchestration
        self.scheduler = None

        # Service clients
        self.forecast_client = ForecastServiceClient(config, "orchestrator-service")
        self.production_client = ProductionServiceClient(config, "orchestrator-service")
        self.procurement_client = ProcurementServiceClient(config, "orchestrator-service")
        self.notification_client = NotificationServiceClient(config, "orchestrator-service")
        self.tenant_client = TenantServiceClient(config)
        self.training_client = TrainingServiceClient(config, "orchestrator-service")
        # Clients for centralized data fetching
        self.inventory_client = InventoryServiceClient(config, "orchestrator-service")
        self.suppliers_client = SuppliersServiceClient(config, "orchestrator-service")
        self.recipes_client = RecipesServiceClient(config, "orchestrator-service")

        # Circuit breakers for each service
        self.forecast_breaker = CircuitBreaker(
            failure_threshold=5,
            timeout_duration=60,
            success_threshold=2
        )
        self.production_breaker = CircuitBreaker(
            failure_threshold=5,
            timeout_duration=60,
            success_threshold=2
        )
        self.procurement_breaker = CircuitBreaker(
            failure_threshold=5,
            timeout_duration=60,
            success_threshold=2
        )
        self.tenant_breaker = CircuitBreaker(
            failure_threshold=3,
            timeout_duration=30,
            success_threshold=2
        )
        self.inventory_breaker = CircuitBreaker(
            failure_threshold=5,
            timeout_duration=60,
            success_threshold=2
        )
        self.suppliers_breaker = CircuitBreaker(
            failure_threshold=5,
            timeout_duration=60,
            success_threshold=2
        )
        self.recipes_breaker = CircuitBreaker(
            failure_threshold=5,
            timeout_duration=60,
            success_threshold=2
        )

    async def emit_orchestration_run_started(
        self,
        tenant_id: uuid.UUID,
        run_id: str,
        run_type: str,  # 'scheduled', 'manual', 'triggered'
        scope: str,  # 'full', 'inventory_only', 'production_only'
    ):
        """
        Emit notification when an orchestration run starts.
        """
        metadata = {
            "run_id": run_id,
            "run_type": run_type,
            "scope": scope,
            "started_at": datetime.now(timezone.utc).isoformat(),
        }

        await self.publisher.publish_notification(
            event_type="operations.orchestration_run_started",
            tenant_id=tenant_id,
            data=metadata
        )

        logger.info(
            "orchestration_run_started_notification_emitted",
            tenant_id=str(tenant_id),
            run_id=run_id
        )

    async def emit_orchestration_run_completed(
        self,
        tenant_id: uuid.UUID,
        run_id: str,
        duration_seconds: float,
        actions_created: int,
        actions_by_type: Dict[str, int],  # e.g., {'purchase_order': 2, 'production_batch': 3}
        status: str = "success",
    ):
        """
        Emit notification when an orchestration run completes.
        """
        # Build message with action summary
        if actions_created == 0:
            action_summary = "No actions needed"
        else:
            action_summary = ", ".join([f"{count} {action_type}" for action_type, count in actions_by_type.items()])

        metadata = {
            "run_id": run_id,
            "status": status,
            "duration_seconds": float(duration_seconds),
            "actions_created": actions_created,
            "actions_by_type": actions_by_type,
            "action_summary": action_summary,
            "completed_at": datetime.now(timezone.utc).isoformat(),
        }

        await self.publisher.publish_notification(
            event_type="operations.orchestration_run_completed",
            tenant_id=tenant_id,
            data=metadata
        )

        logger.info(
            "orchestration_run_completed_notification_emitted",
            tenant_id=str(tenant_id),
            run_id=run_id,
            actions_created=actions_created
        )

    async def emit_action_created_notification(
        self,
        tenant_id: uuid.UUID,
        run_id: str,
        action_id: str,
        action_type: str,  # 'purchase_order', 'production_batch', 'inventory_adjustment'
        action_details: Dict[str, Any],  # Type-specific details
        reason: str,
        estimated_impact: Optional[Dict[str, Any]] = None,
    ):
        """
        Emit notification when the orchestrator creates an action.
        """
        metadata = {
            "run_id": run_id,
            "action_id": action_id,
            "action_type": action_type,
            "action_details": action_details,
            "reason": reason,
            "estimated_impact": estimated_impact,
            "created_at": datetime.now(timezone.utc).isoformat(),
        }

        await self.publisher.publish_notification(
            event_type="operations.action_created",
            tenant_id=tenant_id,
            data=metadata
        )

        logger.info(
            "action_created_notification_emitted",
            tenant_id=str(tenant_id),
            action_id=action_id,
            action_type=action_type
        )

    async def emit_action_completed_notification(
        self,
        tenant_id: uuid.UUID,
        action_id: str,
        action_type: str,
        action_status: str,  # 'approved', 'completed', 'rejected', 'cancelled'
        completed_by: Optional[str] = None,
    ):
        """
        Emit notification when an orchestrator action is completed/resolved.
        """
        metadata = {
            "action_id": action_id,
            "action_type": action_type,
            "action_status": action_status,
            "completed_by": completed_by,
            "completed_at": datetime.now(timezone.utc).isoformat(),
        }

        await self.publisher.publish_notification(
            event_type="operations.action_completed",
            tenant_id=tenant_id,
            data=metadata
        )

        logger.info(
            "action_completed_notification_emitted",
            tenant_id=str(tenant_id),
            action_id=action_id,
            action_status=action_status
        )

    async def run_daily_orchestration(self):
        """
        Main orchestration workflow - runs daily
        Executes for all active tenants in parallel (with limits)
        """
        if not settings.ORCHESTRATION_ENABLED:
            logger.info("Orchestration disabled via config")
            return

        logger.info("Starting daily orchestration workflow")

        try:
            # Get all active tenants
            active_tenants = await self._get_active_tenants()

            if not active_tenants:
                logger.warning("No active tenants found for orchestration")
                return

            logger.info("Processing tenants",
                       total_tenants=len(active_tenants))

            # Process tenants with concurrency limit
            semaphore = asyncio.Semaphore(settings.MAX_CONCURRENT_TENANTS)

            async def process_with_semaphore(tenant_id):
                async with semaphore:
                    return await self._orchestrate_tenant(tenant_id)

            # Process all tenants in parallel (but limited by semaphore)
            tasks = [process_with_semaphore(tenant_id) for tenant_id in active_tenants]
            results = await asyncio.gather(*tasks, return_exceptions=True)

            # Log summary
            successful = sum(1 for r in results if r and not isinstance(r, Exception))
            failed = len(results) - successful

            logger.info("Daily orchestration completed",
                       total_tenants=len(active_tenants),
                       successful=successful,
                       failed=failed)

        except Exception as e:
            logger.error("Error in daily orchestration",
                        error=str(e), exc_info=True)

    async def _orchestrate_tenant(self, tenant_id: uuid.UUID) -> bool:
        """
        Orchestrate workflow for a single tenant using Saga pattern
        Returns True if successful, False otherwise
        """
        logger.info("Starting orchestration for tenant", tenant_id=str(tenant_id))

        # Create orchestration run record
        async with self.config.database_manager.get_session() as session:
            repo = OrchestrationRunRepository(session)
            run_number = await repo.generate_run_number()

            run = await repo.create_run({
                'run_number': run_number,
                'tenant_id': tenant_id,
                'status': OrchestrationStatus.running,
                'run_type': 'scheduled',
                'started_at': datetime.now(timezone.utc),
                'triggered_by': 'scheduler'
            })
            await session.commit()
            run_id = run.id

        try:
            # Emit orchestration started event
            await self.emit_orchestration_run_started(
                tenant_id=tenant_id,
                run_id=str(run_id),
                run_type='scheduled',
                scope='full'
            )

            # Set timeout for entire tenant orchestration
            async with asyncio.timeout(settings.TENANT_TIMEOUT_SECONDS):
                # Execute orchestration using Saga pattern
                # AI enhancement is enabled via ORCHESTRATION_USE_AI_INSIGHTS config
                saga = OrchestrationSaga(
                    forecast_client=self.forecast_client,
                    production_client=self.production_client,
                    procurement_client=self.procurement_client,
                    notification_client=self.notification_client,
                    inventory_client=self.inventory_client,
                    suppliers_client=self.suppliers_client,
                    recipes_client=self.recipes_client,
                    training_client=self.training_client,
                    use_ai_enhancement=settings.ORCHESTRATION_USE_AI_INSIGHTS,
                    ai_insights_base_url=settings.AI_INSIGHTS_SERVICE_URL,
                    ai_insights_min_confidence=settings.AI_INSIGHTS_MIN_CONFIDENCE,
                    # Pass circuit breakers to saga for fault tolerance
                    forecast_breaker=self.forecast_breaker,
                    production_breaker=self.production_breaker,
                    procurement_breaker=self.procurement_breaker,
                    inventory_breaker=self.inventory_breaker,
                    suppliers_breaker=self.suppliers_breaker,
                    recipes_breaker=self.recipes_breaker
                )

                result = await saga.execute_orchestration(
                    tenant_id=str(tenant_id),
                    orchestration_run_id=str(run_id)
                )

                if result['success']:
                    # Update orchestration run with saga results
                    await self._complete_orchestration_run_with_saga(
                        run_id,
                        result
                    )

                    # Emit orchestration completed event
                    await self.emit_orchestration_run_completed(
                        tenant_id=tenant_id,
                        run_id=str(run_id),
                        duration_seconds=result.get('duration_seconds', 0),
                        actions_created=result.get('total_actions', 0),
                        actions_by_type=result.get('actions_by_type', {}),
                        status='success'
                    )

                    logger.info("Tenant orchestration completed successfully",
                               tenant_id=str(tenant_id), run_id=str(run_id))
                    return True
                else:
                    # Saga failed (with compensation)
                    await self._mark_orchestration_failed(
                        run_id,
                        result.get('error', 'Saga execution failed')
                    )

                    # Emit orchestration failed event
                    await self.emit_orchestration_run_completed(
                        tenant_id=tenant_id,
                        run_id=str(run_id),
                        duration_seconds=result.get('duration_seconds', 0),
                        actions_created=0,
                        actions_by_type={},
                        status='failed'
                    )

                    return False

        except asyncio.TimeoutError:
            logger.error("Tenant orchestration timeout",
                        tenant_id=str(tenant_id),
                        timeout_seconds=settings.TENANT_TIMEOUT_SECONDS)
            await self._mark_orchestration_failed(run_id, "Timeout exceeded")
            return False

        except Exception as e:
            logger.error("Tenant orchestration failed",
                        tenant_id=str(tenant_id),
                        error=str(e), exc_info=True)
            await self._mark_orchestration_failed(run_id, str(e))
            return False

    async def _get_active_tenants(self) -> List[uuid.UUID]:
        """
        Get list of active tenants for orchestration

        REAL IMPLEMENTATION (no stubs)
        """
        try:
            logger.info("Fetching active tenants from Tenant Service")

            # Call Tenant Service with circuit breaker
            tenants_data = await self.tenant_breaker.call(
                self.tenant_client.get_active_tenants
            )

            if not tenants_data:
                logger.warning("Tenant Service returned no active tenants")
                return []

            # Extract tenant IDs
            tenant_ids = []
            for tenant in tenants_data:
                tenant_id = tenant.get('id') or tenant.get('tenant_id')
                if tenant_id:
                    # Convert string to UUID if needed
                    if isinstance(tenant_id, str):
                        tenant_id = uuid.UUID(tenant_id)
                    tenant_ids.append(tenant_id)

            logger.info(f"Found {len(tenant_ids)} active tenants for orchestration")

            return tenant_ids

        except CircuitBreakerOpenError:
            logger.error("Circuit breaker open for Tenant Service, skipping orchestration")
            return []

        except Exception as e:
            logger.error("Error getting active tenants", error=str(e), exc_info=True)
            return []

    async def _complete_orchestration_run_with_saga(
        self,
        run_id: uuid.UUID,
        saga_result: Dict[str, Any]
    ):
        """
        Complete orchestration run with saga results

        Args:
            run_id: Orchestration run ID
            saga_result: Result from saga execution
        """
        async with self.config.database_manager.get_session() as session:
            repo = OrchestrationRunRepository(session)
            run = await repo.get_run_by_id(run_id)

            if run:
                started_at = run.started_at
                completed_at = datetime.now(timezone.utc)
                duration = (completed_at - started_at).total_seconds()

                # Extract results from saga
                forecast_id = saga_result.get('forecast_id')
                production_schedule_id = saga_result.get('production_schedule_id')
                procurement_plan_id = saga_result.get('procurement_plan_id')
                notifications_sent = saga_result.get('notifications_sent', 0)

                # Get saga summary
                saga_summary = saga_result.get('saga_summary', {})
                total_steps = saga_summary.get('total_steps', 0)
                completed_steps = saga_summary.get('completed_steps', 0)

                # Extract actual counts from saga result (no placeholders)
                forecast_data = saga_result.get('forecast_data', {})
                production_data = saga_result.get('production_data', {})
                procurement_data = saga_result.get('procurement_data', {})

                forecasts_generated = forecast_data.get('forecasts_created', 0)
                production_batches_created = production_data.get('batches_created', 0)
                purchase_orders_created = procurement_data.get('pos_created', 0)

                # Extract AI insights tracking
                ai_insights_generated = saga_result.get('ai_insights_generated', 0)
                ai_insights_posted = saga_result.get('ai_insights_posted', 0)
                ai_insights_errors = saga_result.get('ai_insights_errors', [])

                # Generate reasoning metadata for the orchestrator context
                reasoning_metadata = self._generate_reasoning_metadata(
                    forecast_data,
                    production_data,
                    procurement_data,
                    ai_insights_generated,
                    ai_insights_posted
                )

                await repo.update_run(run_id, {
                    'status': OrchestrationStatus.completed,
                    'completed_at': completed_at,
                    'duration_seconds': int(duration),
                    'forecast_id': forecast_id,
                    'forecasting_status': 'success',
                    'forecasting_completed_at': completed_at,
                    'forecasts_generated': forecasts_generated,
                    'production_schedule_id': production_schedule_id,
                    'production_status': 'success',
                    'production_completed_at': completed_at,
                    'production_batches_created': production_batches_created,
                    'procurement_plan_id': procurement_plan_id,
                    'procurement_status': 'success',
                    'procurement_completed_at': completed_at,
                    'procurement_plans_created': 1,  # Always 1 plan per orchestration
                    'purchase_orders_created': purchase_orders_created,
                    'notification_status': 'success',
                    'notification_completed_at': completed_at,
                    'notifications_sent': notifications_sent,
                    'ai_insights_status': 'success' if not ai_insights_errors else 'partial',
                    'ai_insights_generated': ai_insights_generated,
                    'ai_insights_posted': ai_insights_posted,
                    'ai_insights_completed_at': completed_at,
                    'saga_steps_total': total_steps,
                    'saga_steps_completed': completed_steps,
                    'run_metadata': reasoning_metadata
                })
                await session.commit()

    def _generate_reasoning_metadata(
        self,
        forecast_data: Dict[str, Any],
        production_data: Dict[str, Any],
        procurement_data: Dict[str, Any],
        ai_insights_generated: int,
        ai_insights_posted: int
    ) -> Dict[str, Any]:
        """
        Generate reasoning metadata for orchestration run that will be used by alert processor.

        This creates structured reasoning data that the alert processor can use to provide
        context when showing AI reasoning to users.
        """
        reasoning_metadata = {
            'reasoning': {
                'type': 'daily_orchestration_summary',
                'timestamp': datetime.now(timezone.utc).isoformat(),
                'summary': 'Daily orchestration run completed successfully',
                'details': {}
            },
            'purchase_orders': [],
            'production_batches': [],
            'ai_insights': {
                'generated': ai_insights_generated,
                'posted': ai_insights_posted
            }
        }

        # Add forecast reasoning
        if forecast_data:
            reasoning_metadata['reasoning']['details']['forecasting'] = {
                'forecasts_created': forecast_data.get('forecasts_created', 0),
                'method': 'automated_daily_forecast',
                'reasoning': 'Generated forecasts based on historical patterns and seasonal trends'
            }

        # Add production reasoning
        if production_data:
            reasoning_metadata['reasoning']['details']['production'] = {
                'batches_created': production_data.get('batches_created', 0),
                'method': 'demand_based_scheduling',
                'reasoning': 'Scheduled production batches based on forecasted demand and inventory levels'
            }

        # Add procurement reasoning
        if procurement_data:
            reasoning_metadata['reasoning']['details']['procurement'] = {
                'requirements_created': procurement_data.get('requirements_created', 0),
                'pos_created': procurement_data.get('pos_created', 0),
                'method': 'automated_procurement',
                'reasoning': 'Generated procurement plan based on production needs and inventory optimization'
            }

        # Add purchase order details with reasoning
        if procurement_data and procurement_data.get('purchase_orders'):
            for po in procurement_data['purchase_orders']:
                po_reasoning = {
                    'id': po.get('id'),
                    'status': po.get('status', 'created'),
                    'delivery_date': po.get('delivery_date'),
                    'reasoning': {
                        'type': 'inventory_optimization',
                        'parameters': {
                            'trigger': 'low_stock_prediction',
                            'min_depletion_days': po.get('min_depletion_days', 3),
                            'quantity': po.get('quantity'),
                            'unit': po.get('unit'),
                            'supplier': po.get('supplier_name'),
                            'financial_impact_eur': po.get('estimated_savings_eur', 0)
                        }
                    }
                }
                reasoning_metadata['purchase_orders'].append(po_reasoning)

        # Add production batch details with reasoning
        if production_data and production_data.get('production_batches'):
            for batch in production_data['production_batches']:
                batch_reasoning = {
                    'id': batch.get('id'),
                    'status': batch.get('status', 'scheduled'),
                    'scheduled_date': batch.get('scheduled_date'),
                    'reasoning': {
                        'type': 'demand_forecasting',
                        'parameters': {
                            'trigger': 'forecasted_demand',
                            'forecasted_quantity': batch.get('forecasted_quantity'),
                            'product_name': batch.get('product_name'),
                            'financial_impact_eur': batch.get('estimated_revenue_eur', 0)
                        }
                    }
                }
                reasoning_metadata['production_batches'].append(batch_reasoning)

        return reasoning_metadata

    async def _mark_orchestration_failed(self, run_id: uuid.UUID, error_message: str):
        """Mark orchestration run as failed"""
        async with self.config.database_manager.get_session() as session:
            repo = OrchestrationRunRepository(session)
            run = await repo.get_run_by_id(run_id)

            if run:
                started_at = run.started_at
                completed_at = datetime.now(timezone.utc)
                duration = (completed_at - started_at).total_seconds()

                await repo.update_run(run_id, {
                    'status': OrchestrationStatus.failed,
                    'completed_at': completed_at,
                    'duration_seconds': int(duration),
                    'error_message': error_message
                })
                await session.commit()

    # Manual trigger for testing
    async def trigger_orchestration_for_tenant(
        self,
        tenant_id: uuid.UUID,
        test_scenario: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Manually trigger orchestration for a tenant (for testing)

        Args:
            tenant_id: Tenant ID to orchestrate
            test_scenario: Optional test scenario (full, production_only, procurement_only)

        Returns:
            Dict with orchestration results
        """
        logger.info("Manual orchestration trigger",
                   tenant_id=str(tenant_id),
                   test_scenario=test_scenario)

        success = await self._orchestrate_tenant(tenant_id)

        return {
            'success': success,
            'tenant_id': str(tenant_id),
            'test_scenario': test_scenario,
            'message': 'Orchestration completed' if success else 'Orchestration failed'
        }

    async def start(self):
        """Start the orchestrator scheduler service"""
        if not settings.ORCHESTRATION_ENABLED:
            logger.info("Orchestration disabled via config")
            return

        # Initialize APScheduler
        self.scheduler = AsyncIOScheduler()

        # Add daily orchestration job
        self.scheduler.add_job(
            self.run_daily_orchestration,
            trigger=CronTrigger(
                hour=settings.ORCHESTRATION_HOUR,
                minute=settings.ORCHESTRATION_MINUTE
            ),
            id='daily_orchestration',
            name='Daily Orchestration Workflow',
            replace_existing=True,
            max_instances=1,
            coalesce=True
        )

        # Start the scheduler
        self.scheduler.start()

        # Log next run time
        next_run = self.scheduler.get_job('daily_orchestration').next_run_time
        logger.info(
            "OrchestratorSchedulerService started with daily job",
            orchestration_hour=settings.ORCHESTRATION_HOUR,
            orchestration_minute=settings.ORCHESTRATION_MINUTE,
            next_run=next_run.isoformat() if next_run else None
        )

    async def stop(self):
        """Stop the orchestrator scheduler service"""
        if self.scheduler and self.scheduler.running:
            self.scheduler.shutdown(wait=True)
            logger.info("OrchestratorSchedulerService stopped")
        else:
            logger.info("OrchestratorSchedulerService already stopped")

    def get_circuit_breaker_stats(self) -> Dict[str, Any]:
        """Get circuit breaker statistics for monitoring"""
        return {
            'forecast_service': self.forecast_breaker.get_stats(),
            'production_service': self.production_breaker.get_stats(),
            'procurement_service': self.procurement_breaker.get_stats(),
            'tenant_service': self.tenant_breaker.get_stats(),
            'inventory_service': self.inventory_breaker.get_stats(),
            'suppliers_service': self.suppliers_breaker.get_stats(),
            'recipes_service': self.recipes_breaker.get_stats()
        }