# ================================================================
# services/orchestrator/app/repositories/orchestration_run_repository.py
# ================================================================
"""
Orchestration Run Repository - Database operations for orchestration audit trail
"""

import uuid
from datetime import datetime, date, timezone
from typing import List, Optional, Dict, Any
from sqlalchemy import select, and_, desc, func
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.orchestration_run import OrchestrationRun, OrchestrationStatus


class OrchestrationRunRepository:
    """Repository for orchestration run operations"""

    def __init__(self, db: AsyncSession):
        self.db = db

    async def create_run(self, run_data: Dict[str, Any]) -> OrchestrationRun:
        """Create a new orchestration run"""
        run = OrchestrationRun(**run_data)
        self.db.add(run)
        await self.db.flush()
        return run

    async def get_run_by_id(self, run_id: uuid.UUID) -> Optional[OrchestrationRun]:
        """Get orchestration run by ID"""
        stmt = select(OrchestrationRun).where(OrchestrationRun.id == run_id)
        result = await self.db.execute(stmt)
        return result.scalar_one_or_none()

    async def update_run(self, run_id: uuid.UUID, updates: Dict[str, Any]) -> Optional[OrchestrationRun]:
        """Update orchestration run"""
        run = await self.get_run_by_id(run_id)
        if not run:
            return None

        for key, value in updates.items():
            if hasattr(run, key):
                setattr(run, key, value)

        run.updated_at = datetime.now(timezone.utc)
        await self.db.flush()
        return run

    async def list_runs(
        self,
        tenant_id: Optional[uuid.UUID] = None,
        status: Optional[OrchestrationStatus] = None,
        start_date: Optional[date] = None,
        end_date: Optional[date] = None,
        limit: int = 50,
        offset: int = 0
    ) -> List[OrchestrationRun]:
        """List orchestration runs with filters"""
        conditions = []

        if tenant_id:
            conditions.append(OrchestrationRun.tenant_id == tenant_id)
        if status:
            conditions.append(OrchestrationRun.status == status)
        if start_date:
            conditions.append(func.date(OrchestrationRun.started_at) >= start_date)
        if end_date:
            conditions.append(func.date(OrchestrationRun.started_at) <= end_date)

        stmt = (
            select(OrchestrationRun)
            .where(and_(*conditions) if conditions else True)
            .order_by(desc(OrchestrationRun.started_at))
            .limit(limit)
            .offset(offset)
        )

        result = await self.db.execute(stmt)
        return result.scalars().all()

    async def get_latest_run_for_tenant(self, tenant_id: uuid.UUID) -> Optional[OrchestrationRun]:
        """Get the most recent orchestration run for a tenant"""
        stmt = (
            select(OrchestrationRun)
            .where(OrchestrationRun.tenant_id == tenant_id)
            .order_by(desc(OrchestrationRun.started_at))
            .limit(1)
        )

        result = await self.db.execute(stmt)
        return result.scalar_one_or_none()

    async def generate_run_number(self) -> str:
        """
        Generate unique run number atomically using database-level counting.

        Uses MAX(run_number) + 1 approach to avoid race conditions
        between reading count and inserting new record.
        """
        today = date.today()
        date_str = today.strftime("%Y%m%d")

        # Get the highest run number for today atomically
        # Using MAX on run_number suffix to avoid counting which has race conditions
        stmt = select(func.max(OrchestrationRun.run_number)).where(
            OrchestrationRun.run_number.like(f"ORCH-{date_str}-%")
        )
        result = await self.db.execute(stmt)
        max_run_number = result.scalar()

        if max_run_number:
            # Extract the numeric suffix and increment it
            try:
                suffix = int(max_run_number.split('-')[-1])
                next_number = suffix + 1
            except (ValueError, IndexError):
                # Fallback to 1 if parsing fails
                next_number = 1
        else:
            # No runs for today yet
            next_number = 1

        return f"ORCH-{date_str}-{next_number:04d}"

    async def get_failed_runs(self, limit: int = 10) -> List[OrchestrationRun]:
        """Get recent failed orchestration runs"""
        stmt = (
            select(OrchestrationRun)
            .where(OrchestrationRun.status == OrchestrationStatus.failed)
            .order_by(desc(OrchestrationRun.started_at))
            .limit(limit)
        )

        result = await self.db.execute(stmt)
        return result.scalars().all()

    async def get_run_statistics(
        self,
        start_date: Optional[date] = None,
        end_date: Optional[date] = None
    ) -> Dict[str, Any]:
        """Get orchestration run statistics"""
        conditions = []
        if start_date:
            conditions.append(func.date(OrchestrationRun.started_at) >= start_date)
        if end_date:
            conditions.append(func.date(OrchestrationRun.started_at) <= end_date)

        where_clause = and_(*conditions) if conditions else True

        # Total runs
        total_stmt = select(func.count(OrchestrationRun.id)).where(where_clause)
        total_result = await self.db.execute(total_stmt)
        total_runs = total_result.scalar() or 0

        # Successful runs
        success_stmt = select(func.count(OrchestrationRun.id)).where(
            and_(
                where_clause,
                OrchestrationRun.status == OrchestrationStatus.completed
            )
        )
        success_result = await self.db.execute(success_stmt)
        successful_runs = success_result.scalar() or 0

        # Failed runs
        failed_stmt = select(func.count(OrchestrationRun.id)).where(
            and_(
                where_clause,
                OrchestrationRun.status == OrchestrationStatus.failed
            )
        )
        failed_result = await self.db.execute(failed_stmt)
        failed_runs = failed_result.scalar() or 0

        # Average duration
        avg_duration_stmt = select(func.avg(OrchestrationRun.duration_seconds)).where(
            and_(
                where_clause,
                OrchestrationRun.status == OrchestrationStatus.completed
            )
        )
        avg_duration_result = await self.db.execute(avg_duration_stmt)
        avg_duration = avg_duration_result.scalar() or 0

        return {
            'total_runs': total_runs,
            'successful_runs': successful_runs,
            'failed_runs': failed_runs,
            'success_rate': (successful_runs / total_runs * 100) if total_runs > 0 else 0,
            'average_duration_seconds': float(avg_duration) if avg_duration else 0
        }
