"""
FoundryMatch SaaS - ML Pattern Scanner
======================================
This module provides a service class for mining telemetry data to find
interesting patterns, anomalies, and insights. It can be used to
proactively identify issues, suggest optimizations, or understand user behavior.
"""

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

import asyncpg

from ..events.collectors import TelemetryCollector

log = logging.getLogger(__name__)


@dataclass
class DetectedPattern:
    """
    A structured representation of a pattern found in telemetry data.
    """

    pattern_type: str
    description: str
    severity: str  # e.g., 'info', 'warning', 'critical'
    details: Dict[str, Any]
    recommendation: Optional[str] = None


class PatternScanner:
    """
    A service that connects to the telemetry database and runs various scans
    to detect meaningful patterns.
    """

    def __init__(self, db_pool: asyncpg.Pool):
        """
        Initializes the PatternScanner with a database connection pool.

        Args:
            db_pool: An active asyncpg connection pool.
        """
        self.db_pool = db_pool

    async def scan_all(
        self, time_window: timedelta = timedelta(days=7)
    ) -> List[DetectedPattern]:
        """
        Runs all available scans in parallel over a given time window.

        Args:
            time_window: The duration to look back for telemetry data.

        Returns:
            A list of all detected patterns.
        """
        log.info(
            f"Starting telemetry pattern scan for the last {time_window.days} days."
        )
        start_time = datetime.utcnow() - time_window

        # Run scans in parallel for efficiency
        tasks = [
            self.find_frequently_failing_workflows(start_time),
            self.detect_performance_degradation(start_time),
            self.find_high_rejection_rate_jobs(start_time),
        ]

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

        all_patterns = []
        for i, result in enumerate(results):
            if isinstance(result, Exception):
                log.error(f"Scan task {i} failed: {result}", exc_info=result)
            else:
                all_patterns.extend(result)

        log.info(f"Scan complete. Found {len(all_patterns)} patterns.")
        return all_patterns

    async def find_frequently_failing_workflows(
        self, start_time: datetime
    ) -> List[DetectedPattern]:
        """
        Finds workflows that are frequently failing.
        This assumes an event like 'workflow_failed' is logged in the event_types table.
        """
        patterns = []
        query = """
            SELECT
                workflow_id, account_id, COUNT(*) as failure_count
            FROM telemetry_events te
            JOIN event_types et ON te.event_type_id = et.id
            WHERE et.name = 'workflow_failed' AND te.time >= $1
            GROUP BY workflow_id, account_id
            HAVING COUNT(*) > 5
            ORDER BY failure_count DESC
            LIMIT 10;
        """
        try:
            failing_workflows = await self.db_pool.fetch(query, start_time)
            for wf in failing_workflows:
                patterns.append(
                    DetectedPattern(
                        pattern_type="frequent_workflow_failure",
                        description=f"Workflow {wf['workflow_id']} for account {wf['account_id']} failed {wf['failure_count']} times.",
                        severity="critical",
                        details=dict(wf),
                        recommendation="Investigate the root cause of the failures for this workflow. Check logs associated with the workflow ID.",
                    )
                )
        except Exception as e:
            log.error(f"Error scanning for failing workflows: {e}", exc_info=True)
        return patterns

    async def detect_performance_degradation(
        self, start_time: datetime
    ) -> List[DetectedPattern]:
        """
        Detects if the average execution time for event types is increasing.
        Compares the recent half of the time window to the older half.
        """
        patterns = []
        mid_point = start_time + (datetime.utcnow() - start_time) / 2

        query = """
            WITH recent_stats AS (
                SELECT et.name as event_name, account_id, AVG((performance_metrics->>'execution_time_seconds')::float) as avg_time
                FROM telemetry_events te JOIN event_types et ON te.event_type_id = et.id
                WHERE te.time >= $1 AND te.time < $2 AND performance_metrics ? 'execution_time_seconds'
                GROUP BY et.name, account_id
            ), past_stats AS (
                SELECT et.name as event_name, account_id, AVG((performance_metrics->>'execution_time_seconds')::float) as avg_time
                FROM telemetry_events te JOIN event_types et ON te.event_type_id = et.id
                WHERE te.time >= $3 AND te.time < $1 AND performance_metrics ? 'execution_time_seconds'
                GROUP BY et.name, account_id
            )
            SELECT r.event_name, r.account_id, r.avg_time as recent_avg_time, p.avg_time as past_avg_time
            FROM recent_stats r JOIN past_stats p ON r.event_name = p.event_name AND r.account_id = p.account_id
            WHERE r.avg_time > p.avg_time * 1.5 AND r.avg_time > 1.0;
        """
        try:
            degradations = await self.db_pool.fetch(
                query, mid_point, datetime.utcnow(), start_time
            )
            for deg in degradations:
                patterns.append(
                    DetectedPattern(
                        pattern_type="performance_degradation",
                        description=f"Event '{deg['event_name']}' for account {deg['account_id']} has slowed down from {deg['past_avg_time']:.2f}s to {deg['recent_avg_time']:.2f}s.",
                        severity="warning",
                        details=dict(deg),
                        recommendation="Review recent changes related to this event type. Check for inefficient queries or resource contention.",
                    )
                )
        except Exception as e:
            log.error(f"Error scanning for performance degradation: {e}", exc_info=True)
        return patterns

    async def find_high_rejection_rate_jobs(
        self, start_time: datetime
    ) -> List[DetectedPattern]:
        """Finds jobs with a high rate of 'rejected' feedback using the match_feedback_events table."""
        patterns = []
        query = """
            WITH feedback_counts AS (
                SELECT job_id, account_id, COUNT(*) as total_feedback,
                       SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) as rejected_count
                FROM match_feedback_events WHERE created_at >= $1
                GROUP BY job_id, account_id
            )
            SELECT job_id, account_id, total_feedback, rejected_count, (rejected_count::float / total_feedback) as rejection_rate
            FROM feedback_counts
            WHERE total_feedback > 20 AND (rejected_count::float / total_feedback) > 0.30;
        """
        try:
            high_rejection_jobs = await self.db_pool.fetch(query, start_time)
            for job in high_rejection_jobs:
                patterns.append(
                    DetectedPattern(
                        pattern_type="high_feedback_rejection_rate",
                        description=f"Job {job['job_id']} for account {job['account_id']} has a high feedback rejection rate of {job['rejection_rate']:.1%}.",
                        severity="warning",
                        details=dict(job),
                        recommendation="Review the match configuration for this job. The threshold might be too low or mappings might be incorrect.",
                    )
                )
        except asyncpg.exceptions.UndefinedTableError:
            log.warning(
                "`match_feedback_events` table not found. Skipping rejection rate scan."
            )
        except Exception as e:
            log.error(f"Error scanning for high rejection rates: {e}", exc_info=True)
        return patterns

    async def detect_auto_config_rejected(
        self, start_time: datetime, threshold: float = 0.3
    ) -> List[DetectedPattern]:
        """Detect when auto-configured settings are frequently rejected by users."""
        patterns = []
        query = """
            SELECT
                job_id,
                account_id,
                COUNT(*) FILTER (WHERE metadata->>'auto_configured' = 'true' AND status = 'rejected') as auto_rejected,
                COUNT(*) FILTER (WHERE metadata->>'auto_configured' = 'true') as auto_total
            FROM match_feedback_events
            WHERE created_at >= $1
            GROUP BY job_id, account_id
            HAVING COUNT(*) FILTER (WHERE metadata->>'auto_configured' = 'true') > 5
        """
        try:
            results = await self.db_pool.fetch(query, start_time)
            for row in results:
                if row["auto_total"] > 0:
                    rejection_rate = row["auto_rejected"] / row["auto_total"]
                    if rejection_rate > threshold:
                        pattern = DetectedPattern(
                            pattern_type="auto_config_high_rejection",
                            description=f"Auto-configured settings for job {row['job_id']} have {rejection_rate:.1%} rejection rate",
                            severity="warning",
                            details={
                                "job_id": row["job_id"],
                                "account_id": row["account_id"],
                                "rejection_rate": rejection_rate,
                                "total_auto_configs": row["auto_total"],
                            },
                            recommendation="Review auto-configuration algorithm for this data pattern",
                        )
                        patterns.append(pattern)

                        # Emit telemetry event
                        await self._emit_pattern_event(pattern)
        except Exception as e:
            log.error(f"Error detecting auto-config rejections: {e}", exc_info=True)
        return patterns

    async def find_manual_override_hotspots(
        self, start_time: datetime
    ) -> List[DetectedPattern]:
        """Find columns that are frequently manually edited/overridden."""
        patterns = []
        query = """
            SELECT
                metadata->>'column_name' as column_name,
                COUNT(*) as override_count,
                array_agg(DISTINCT account_id) as affected_accounts
            FROM telemetry_events te
            JOIN event_types et ON te.event_type_id = et.id
            WHERE et.name = 'manual_column_override'
              AND te.time >= $1
              AND metadata ? 'column_name'
            GROUP BY metadata->>'column_name'
            HAVING COUNT(*) > 10
            ORDER BY override_count DESC
            LIMIT 10
        """
        try:
            hotspots = await self.db_pool.fetch(query, start_time)
            for spot in hotspots:
                pattern = DetectedPattern(
                    pattern_type="manual_override_hotspot",
                    description=f"Column '{spot['column_name']}' manually overridden {spot['override_count']} times",
                    severity="info",
                    details={
                        "column_name": spot["column_name"],
                        "override_count": spot["override_count"],
                        "affected_accounts": len(spot["affected_accounts"]),
                    },
                    recommendation=f"Consider improving auto-detection for '{spot['column_name']}' columns",
                )
                patterns.append(pattern)

                # Emit telemetry event
                await self._emit_pattern_event(pattern)
        except Exception as e:
            log.error(f"Error finding manual override hotspots: {e}", exc_info=True)
        return patterns

    async def detect_ttf_result_drift(
        self, start_time: datetime
    ) -> List[DetectedPattern]:
        """Detect if time-to-first-result is trending upward."""
        patterns = []
        mid_point = start_time + (datetime.utcnow() - start_time) / 2

        query = """
            WITH recent_ttfr AS (
                SELECT
                    account_id,
                    AVG(EXTRACT(EPOCH FROM (metadata->>'first_result_time')::timestamp -
                                           (metadata->>'job_start_time')::timestamp)) as avg_ttfr
                FROM telemetry_events te
                JOIN event_types et ON te.event_type_id = et.id
                WHERE et.name = 'job_first_result'
                  AND te.time >= $1
                  AND metadata ? 'first_result_time'
                  AND metadata ? 'job_start_time'
                GROUP BY account_id
            ),
            past_ttfr AS (
                SELECT
                    account_id,
                    AVG(EXTRACT(EPOCH FROM (metadata->>'first_result_time')::timestamp -
                                           (metadata->>'job_start_time')::timestamp)) as avg_ttfr
                FROM telemetry_events te
                JOIN event_types et ON te.event_type_id = et.id
                WHERE et.name = 'job_first_result'
                  AND te.time >= $2 AND te.time < $1
                  AND metadata ? 'first_result_time'
                  AND metadata ? 'job_start_time'
                GROUP BY account_id
            )
            SELECT
                r.account_id,
                r.avg_ttfr as recent_ttfr,
                p.avg_ttfr as past_ttfr,
                (r.avg_ttfr - p.avg_ttfr) / p.avg_ttfr as pct_increase
            FROM recent_ttfr r
            JOIN past_ttfr p ON r.account_id = p.account_id
            WHERE r.avg_ttfr > p.avg_ttfr * 1.2
              AND r.avg_ttfr > 5.0
        """
        try:
            drifts = await self.db_pool.fetch(query, mid_point, start_time)
            for drift in drifts:
                pattern = DetectedPattern(
                    pattern_type="ttf_result_drift",
                    description=f"Time-to-first-result increased {drift['pct_increase']:.1%} for account {drift['account_id']}",
                    severity="warning",
                    details={
                        "account_id": drift["account_id"],
                        "recent_ttfr": drift["recent_ttfr"],
                        "past_ttfr": drift["past_ttfr"],
                        "pct_increase": drift["pct_increase"],
                    },
                    recommendation="Investigate blocking strategy and data volume changes",
                )
                patterns.append(pattern)

                # Emit telemetry event
                await self._emit_pattern_event(pattern)
        except Exception as e:
            log.error(f"Error detecting TTF result drift: {e}", exc_info=True)
        return patterns

    async def _emit_pattern_event(self, pattern: DetectedPattern):
        """Emit a telemetry event for each detected pattern."""
        try:
            collector = TelemetryCollector()
            await collector.collect(
                [
                    {
                        "type": "ml.pattern_detected",
                        "timestamp": datetime.utcnow().isoformat(),
                        "data": {
                            "pattern_type": pattern.pattern_type,
                            "description": pattern.description,
                            "severity": pattern.severity,
                            "details": pattern.details,
                            "recommendation": pattern.recommendation,
                        },
                    }
                ]
            )
        except Exception as e:
            log.debug(f"Failed to emit pattern telemetry: {e}")
