"""
Logs routes for SAMSON PACS API.

Provides endpoints for:
- Real-time log streaming via SSE
- Log history retrieval
- Log statistics
- Log search and filtering
"""

import os
import json
import asyncio
import redis
import threading
import datetime
from typing import Optional, List, Dict, Any
from queue import Queue, Empty

from fastapi import APIRouter, Query, HTTPException, BackgroundTasks
from fastapi.responses import StreamingResponse
from fastapi.requests import Request

router = APIRouter(prefix="/api/logs", tags=["logs"])

# Redis connection
_redis_client: Optional[redis.Redis] = None

def get_redis_client() -> redis.Redis:
    """Get or create Redis client."""
    global _redis_client

    if _redis_client is None:
        try:
            _redis_client = redis.Redis(
                host=os.environ.get('REDIS_HOST', 'localhost'),
                port=int(os.environ.get('REDIS_PORT', 6379)),
                db=0,
                decode_responses=True,
                socket_connect_timeout=5,
                socket_timeout=5,
                retry_on_timeout=True
            )
            # Test connection
            _redis_client.ping()
        except (redis.ConnectionError, redis.TimeoutError) as e:
            raise HTTPException(
                status_code=503,
                detail=f"Redis connection failed: {str(e)}"
            )

    return _redis_client


@router.get("/stream")
async def stream_logs(
    request: Request,
    limit_history: int = Query(200, description="Number of historical logs to send on connect")
):
    """
    Stream logs in real-time using Server-Sent Events (SSE).

    This endpoint maintains a persistent HTTP connection and streams
    logs as they are generated by the system.

    Example client usage (JavaScript):
    ```javascript
    const eventSource = new EventSource('/api/logs/stream');
    eventSource.onmessage = (event) => {
        const log = JSON.parse(event.data);
        console.log(log);
    };
    ```
    """
    redis_client = get_redis_client()

    async def log_generator():
        """Generate SSE events for logs."""
        # Store original log levels to restore later
        original_levels = {}

        try:
            # First, send recent history if requested
            if limit_history > 0:
                history_logs = redis_client.lrange("pacs:logs", 0, limit_history - 1)

                for log_json in reversed(history_logs):  # Send oldest first
                    try:
                        log_entry = json.loads(log_json)

                        yield f"data: {json.dumps(log_entry, ensure_ascii=False)}\n\n"

                        # Small delay to prevent overwhelming the client
                        await asyncio.sleep(0.001)

                    except json.JSONDecodeError:
                        continue

            # Then, subscribe to real-time logs
            pubsub = redis_client.pubsub()
            pubsub.subscribe("pacs:logs:stream")

            # Send initial subscription confirmation
            yield f"event: connected\ndata: {{\"timestamp\": \"{datetime.datetime.now().isoformat()}\"}}\n\n"

            # Simple non-blocking approach: check for messages with small timeout
            while True:
                try:
                    # Get message with very small timeout (non-blocking)
                    message = pubsub.get_message(ignore_subscribe_messages=True, timeout=0.1)

                    if message and message['type'] == 'message':
                        try:
                            log_entry = json.loads(message['data'])
                            yield f"data: {json.dumps(log_entry, ensure_ascii=False)}\n\n"

                        except json.JSONDecodeError:
                            continue
                    else:
                        # No message, yield control to event loop
                        await asyncio.sleep(0.01)

                except (redis.ConnectionError, redis.TimeoutError):
                    # Try to reconnect
                    await asyncio.sleep(1)
                    try:
                        pubsub.close()
                        pubsub = redis_client.pubsub()
                        pubsub.subscribe("pacs:logs:stream")
                    except:
                        await asyncio.sleep(5)
                except Exception as e:
                    # Log error but continue
                    await asyncio.sleep(1)

        except Exception as e:
            # Send error event before closing
            error_msg = {
                "event": "error",
                "timestamp": datetime.datetime.now().isoformat(),
                "message": f"Log stream error: {str(e)}"
            }
            yield f"data: {json.dumps(error_msg, ensure_ascii=False)}\n\n"
            raise

    return StreamingResponse(
        log_generator(),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Accel-Buffering": "no",  # Disable nginx buffering for SSE
            "Access-Control-Allow-Origin": "*",
        }
    )


def _filter_log(
    log_entry: Dict[str, Any],
    level: Optional[str],
    module: Optional[str],
    logger: Optional[str]
) -> bool:
    """Apply filters to log entry."""
    if level and log_entry.get('level', '').upper() != level.upper():
        return False

    if module and module not in log_entry.get('module', ''):
        return False

    if logger and logger not in log_entry.get('logger', ''):
        return False

    return True


@router.get("/history")
async def get_log_history(
    limit: int = Query(100, ge=1, le=1000, description="Maximum number of logs to return"),
    offset: int = Query(0, ge=0, description="Offset for pagination"),
    level: Optional[str] = Query(None, description="Filter by log level"),
    module: Optional[str] = Query(None, description="Filter by module name"),
    logger: Optional[str] = Query(None, description="Filter by logger name"),
    date_from: Optional[str] = Query(None, description="Start date (YYYY-MM-DD) or date and time (YYYY-MM-DDTHH:MM)"),
    date_to: Optional[str] = Query(None, description="End date (YYYY-MM-DD) or date and time (YYYY-MM-DDTHH:MM)"),
    search: Optional[str] = Query(None, description="Search in log messages")
):
    """
    Get historical logs with filtering and pagination.

    Returns logs in reverse chronological order (newest first).
    """
    redis_client = get_redis_client()

    try:
        # Determine which key to use based on date filters
        if date_from or date_to:
            # Date range query - need to check multiple date keys
            logs = _get_logs_by_date_range(
                redis_client, date_from, date_to, level, module, logger, search
            )
            total = len(logs)

            # Apply pagination
            start = offset
            end = offset + limit
            paginated_logs = logs[start:end]

        else:
            # Get from main logs list
            total = 0 #redis_client.llen("pacs:logs")

            # Get logs with pagination (newest first)
            start = offset
            end = offset + limit - 1
            logs_json = redis_client.lrange("pacs:logs", start, end)

            # Parse and filter logs
            paginated_logs = []
            for log_json in logs_json:
                try:
                    log_entry = json.loads(log_json)

                    # Apply filters
                    if not _filter_log(log_entry, level, module, logger):
                        continue

                    # Apply search filter
                    if search:
                        message = log_entry.get('message', '').lower()
                        search_terms = search.lower().split()
                        if not all(term in message for term in search_terms):
                            continue

                    paginated_logs.append(log_entry)
                    total += 1
                except json.JSONDecodeError:
                    continue

        return {
            "logs": paginated_logs,
            "pagination": {
                "total": total,
                "limit": limit,
                "offset": offset,
                "has_more": offset + len(paginated_logs) < total
            },
            "filters": {
                "level": level,
                "module": module,
                "logger": logger,
                "date_from": date_from,
                "date_to": date_to,
                "search": search
            }
        }

    except (redis.ConnectionError, redis.TimeoutError) as e:
        raise HTTPException(status_code=503, detail=f"Redis error: {str(e)}")


def _get_logs_by_date_range(
    redis_client: redis.Redis,
    date_from: Optional[str],
    date_to: Optional[str],
    level: Optional[str],
    module: Optional[str],
    logger: Optional[str],
    search: Optional[str]
) -> List[Dict[str, Any]]:
    """Get logs for a date range."""
    # Parse dates
    start_date = _parse_date(date_from) if date_from else datetime.datetime.now() - datetime.timedelta(days=7)
    end_date = _parse_date(date_to) if date_to else datetime.datetime.now()

    # If only date was provided (without time), adjust to cover full day
    if date_from and "T" not in date_from:  # Date-only format (no time component)
        start_date = start_date.replace(hour=0, minute=0, second=0, microsecond=0)
    if date_to and "T" not in date_to:  # Date-only format (no time component)
        end_date = end_date.replace(hour=23, minute=59, second=59, microsecond=999999)

    # Generate list of date keys
    date_keys = []
    current_date = start_date
    while current_date <= end_date:
        date_str = current_date.strftime("%Y-%m-%d")
        date_keys.append(f"pacs:logs:by_date:{date_str}")
        current_date += datetime.timedelta(days=1)

    # Collect logs from all date keys
    all_logs = []
    for date_key in date_keys:
        logs_json = redis_client.zrange(date_key, 0, -1)
        for log_json in logs_json:
            try:
                log_entry = json.loads(log_json)

                # Apply filters
                if not _filter_log(log_entry, level, module, logger):
                    continue

                # Apply search filter
                if search:
                    message = log_entry.get('message', '').lower()
                    search_terms = search.lower().split()
                    if not all(term in message for term in search_terms):
                        continue

                # Check timestamp range
                timestamp_str = log_entry['timestamp']
                # Remove timezone if present
                if timestamp_str.endswith('Z'):
                    timestamp_str = timestamp_str[:-1]
                log_time = datetime.datetime.fromisoformat(timestamp_str)
                if start_date <= log_time <= end_date:
                    all_logs.append(log_entry)

            except (json.JSONDecodeError, KeyError, ValueError):
                continue

    # Sort by timestamp (newest first)
    #all_logs.sort(key=lambda x: x.get('timestamp_epoch', 0), reverse=True)

    return all_logs


def _parse_date(date_str: str) -> datetime.datetime:
    """Parse date string to datetime."""
    # Try parsing date and time (YYYY-MM-DDTHH:MM)
    try:
        return datetime.datetime.strptime(date_str, "%Y-%m-%dT%H:%M")
    except ValueError:
        pass

    # Try parsing date only (YYYY-MM-DD)
    try:
        return datetime.datetime.strptime(date_str, "%Y-%m-%d")
    except ValueError:
        raise HTTPException(status_code=400, detail=f"Invalid date format: {date_str}. Use YYYY-MM-DD or YYYY-MM-DDTHH:MM")


@router.get("/stats")
async def get_log_stats(
    period: str = Query("24h", description="Time period: 1h, 24h, 7d, 30d")
):
    """
    Get logging statistics.

    Returns counts by log level and other statistics.
    """
    redis_client = get_redis_client()

    try:
        stats = {}
        levels = ['debug', 'info', 'warning', 'error', 'critical']

        # Get counts by level
        for level in levels:
            key = f"pacs:logs:stats:{level}"
            count = redis_client.get(key)
            stats[level] = int(count) if count else 0

        # Get total logs count
        stats['total_logs'] = redis_client.llen("pacs:logs")

        # Get Redis info
        redis_info = redis_client.info()
        stats['redis'] = {
            'used_memory': redis_info.get('used_memory_human', 'N/A'),
            'connected_clients': redis_info.get('connected_clients', 0),
            'total_commands_processed': redis_info.get('total_commands_processed', 0),
        }

        return {
            "period": period,
            "timestamp": datetime.datetime.now().isoformat(),
            "statistics": stats
        }

    except (redis.ConnectionError, redis.TimeoutError) as e:
        raise HTTPException(status_code=503, detail=f"Redis error: {str(e)}")


@router.get("/levels")
async def get_log_levels():
    """
    Get available log levels.

    Returns the list of log levels used in the system.
    """
    return {
        "levels": [
            {"name": "DEBUG", "value": 10, "description": "Detailed debug information"},
            {"name": "INFO", "value": 20, "description": "Confirmation that things are working"},
            {"name": "WARNING", "value": 30, "description": "Something unexpected happened"},
            {"name": "ERROR", "value": 40, "description": "Something failed"},
            {"name": "CRITICAL", "value": 50, "description": "Serious failure"}
        ],
        "default_level": os.environ.get('LOG_LEVEL', 'INFO')
    }


@router.post("/clear")
async def clear_logs(
    background_tasks: BackgroundTasks,
    older_than_days: int = Query(7, ge=1, le=365, description="Clear logs older than N days")
):
    """
    Clear old logs from Redis.

    This operation runs in the background and may take some time.
    """
    redis_client = get_redis_client()

    # Schedule cleanup in background
    background_tasks.add_task(_clear_old_logs_task, redis_client, older_than_days)

    return {
        "message": f"Log cleanup scheduled for logs older than {older_than_days} days",
        "timestamp": datetime.datetime.now().isoformat(),
        "task_id": f"clear_logs_{datetime.datetime.now().timestamp()}"
    }


async def _clear_old_logs_task(redis_client: redis.Redis, older_than_days: int):
    """Background task to clear old logs."""
    try:
        cutoff_date = datetime.datetime.now() - datetime.timedelta(days=older_than_days)
        cutoff_timestamp = cutoff_date.timestamp()

        # Get all date keys
        date_keys = []
        cursor = 0
        while True:
            cursor, keys = redis_client.scan(
                cursor=cursor,
                match="pacs:logs:by_date:*",
                count=100
            )
            date_keys.extend(keys)
            if cursor == 0:
                break

        # Remove old date keys and their contents
        deleted_count = 0
        for date_key in date_keys:
            # Extract date from key
            try:
                date_str = date_key.split(":")[-1]
                key_date = datetime.datetime.strptime(date_str, "%Y-%m-%d")

                if key_date < cutoff_date:
                    # Remove the entire date key
                    redis_client.delete(date_key)
                    deleted_count += 1

            except (ValueError, IndexError):
                continue

        # Also clean up old logs from main list (this is more complex)
        # For now, rely on Redis TTL and maxmemory policy

        # Log the cleanup
        cleanup_log = {
            "timestamp": datetime.datetime.now().isoformat(),
            "level": "INFO",
            "module": "logs",
            "logger": "pacs.api.routes.logs",
            "message": f"Cleaned up {deleted_count} date keys older than {older_than_days} days"
        }

        # Publish cleanup log
        redis_client.publish("pacs:logs:stream", json.dumps(cleanup_log))

    except Exception as e:
        # Log error but don't raise (background task)
        error_log = {
            "timestamp": datetime.datetime.now().isoformat(),
            "level": "ERROR",
            "module": "logs",
            "logger": "pacs.api.routes.logs",
            "message": f"Log cleanup failed: {str(e)}"
        }
        try:
            redis_client.publish("pacs:logs:stream", json.dumps(error_log))
        except:
            pass


@router.get("/health")
async def logs_health():
    """
    Check health of logs system.

    Returns status of Redis connection and log system.
    """
    try:
        redis_client = get_redis_client()

        # Test Redis connection
        redis_client.ping()

        # Get some basic stats
        total_logs = redis_client.llen("pacs:logs")
        pubsub_clients = len(redis_client.pubsub_channels("pacs:logs:stream"))

        return {
            "status": "healthy",
            "timestamp": datetime.datetime.now().isoformat(),
            "redis": {
                "connected": True,
                "host": os.environ.get('REDIS_HOST', 'localhost'),
                "port": int(os.environ.get('REDIS_PORT', 6379))
            },
            "logs": {
                "total_count": total_logs,
                "stream_clients": pubsub_clients,
                "retention_days": 7  # Default TTL
            }
        }

    except (redis.ConnectionError, redis.TimeoutError) as e:
        return {
            "status": "unhealthy",
            "timestamp": datetime.datetime.now().isoformat(),
            "error": f"Redis connection failed: {str(e)}",
            "redis": {
                "connected": False,
                "host": os.environ.get('REDIS_HOST', 'localhost'),
                "port": int(os.environ.get('REDIS_PORT', 6379))
            }
        }
