"""
Memory management API routes
"""

import logging
from typing import List, Optional
from datetime import datetime, timedelta, timezone
from fastapi import APIRouter, Depends, Query, Request, UploadFile, File
from fastapi.responses import Response
from slowapi import Limiter
from slowapi.util import get_remote_address

from ...models.request import (
    MemoryCreateRequest,
    MemoryBatchCreateRequest,
    MemoryUpdateRequest,
    MemoryBatchUpdateRequest,
    BulkDeleteRequest,
)
from ...models.response import (
    APIResponse,
    MemoryListResponse,
    SessionListResponse,
    SessionStatsResponse,
    TimelineResponse,
)
from ...services.memory_service import MemoryService
from ...middleware.auth import verify_api_key
from ...middleware.rate_limit import limiter, get_rate_limit_string
from ...utils.converters import memory_dict_to_response

logger = logging.getLogger("server")

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


TIME_RANGE_PATTERN = "^(7d|30d|90d|all)$"


def parse_time_range_cutoff(time_range: Optional[str]) -> Optional[datetime]:
    """Convert a preset time-range string ('7d' / '30d' / '90d' / 'all') into a UTC cutoff datetime.

    Returns None when the range is empty, 'all', or unparseable — meaning no time filter applies.
    """
    if not time_range or time_range == "all":
        return None
    try:
        days = int(time_range[:-1])
    except (TypeError, ValueError):
        return None
    return datetime.now(timezone.utc) - timedelta(days=days)


def get_memory_service(request: Request) -> MemoryService:
    """Dependency to get memory service singleton from app state"""
    service = request.app.state.memory_service
    if service is None:
        from ...models.errors import ErrorCode, APIError
        raise APIError(
            code=ErrorCode.INTERNAL_ERROR,
            message="Memory service unavailable: storage backend initialization failed",
            status_code=503,
        )
    return service


@router.post(
    "",
    response_model=APIResponse,
    summary="Create a memory",
    description="Create a new memory with optional user_id, agent_id, and metadata",
)
@limiter.limit(get_rate_limit_string())
async def create_memory(
    request: Request,
    body: MemoryCreateRequest,
    api_key: str = Depends(verify_api_key),
    service: MemoryService = Depends(get_memory_service),
):
    """Create a new memory"""
    results = service.create_memory(
        content=body.content,
        user_id=body.user_id,
        agent_id=body.agent_id,
        run_id=body.run_id,
        metadata=body.metadata,
        filters=body.filters,
        scope=body.scope,
        memory_type=body.memory_type,
        infer=body.infer,
    )
    
    # Convert all created memories to response format
    # results is now a list of memory dictionaries
    memory_responses = [memory_dict_to_response(m) for m in results]
    
    # Always return array of memories
    # Exclude None values to avoid returning null fields
    if len(memory_responses) == 0:
        message = "No memories were created (likely duplicates detected or no facts extracted)"
    elif len(memory_responses) == 1:
        message = "Memory created successfully"
    else:
        message = f"Created {len(memory_responses)} memories successfully"
    
    return APIResponse(
        success=True,
        data=[m.model_dump(mode='json', exclude_none=True) for m in memory_responses],
        message=message,
    )


@router.post(
    "/batch",
    response_model=APIResponse,
    summary="Create multiple memories",
    description="Create multiple memories in a single request (batch operation)",
)
@limiter.limit(get_rate_limit_string())
async def batch_create_memories(
    request: Request,
    body: MemoryBatchCreateRequest,
    api_key: str = Depends(verify_api_key),
    service: MemoryService = Depends(get_memory_service),
):
    """Create multiple memories in batch"""
    # Convert MemoryItem objects to dictionaries
    memories_data = [
        {
            "content": item.content,
            "metadata": item.metadata,
            "filters": item.filters,
            "scope": item.scope,
            "memory_type": item.memory_type,
        }
        for item in body.memories
    ]
    
    result = service.batch_create_memories(
        memories=memories_data,
        user_id=body.user_id,
        agent_id=body.agent_id,
        run_id=body.run_id,
        infer=body.infer,
    )
    
    # Convert created memories to response format
    created_memories = []
    for item in result["created"]:
        try:
            memory = service.get_memory(
                memory_id=item["memory_id"],
                user_id=body.user_id,
                agent_id=body.agent_id,
            )
            created_memories.append(memory_dict_to_response(memory).model_dump(mode='json'))
        except Exception as e:
            logger.warning(f"Failed to retrieve created memory {item['memory_id']}: {e}")
            # Include basic info even if full retrieval fails
            created_memories.append({
                "memory_id": item["memory_id"],
                "content": item["content"],
            })
    
    response_data = {
        "memories": created_memories,
        "total": result["total"],
        "created_count": result["created_count"],
        "failed_count": result["failed_count"],
    }
    
    # Only include failed items if there are any
    if result["failed_count"] > 0:
        response_data["failed"] = result["failed"]
    
    return APIResponse(
        success=True,
        data=response_data,
        message=f"Created {result['created_count']} out of {result['total']} memories",
    )


@router.get(
    "",
    response_model=APIResponse,
    summary="List memories",
    description="Get a list of memories with optional filtering, pagination, and sorting",
)
@limiter.limit(get_rate_limit_string())
async def list_memories(
    request: Request,
    user_id: Optional[str] = Query(None, description="Filter by user ID"),
    agent_id: Optional[str] = Query(None, description="Filter by agent ID"),
    run_id: Optional[str] = Query(None, description="Filter by run ID"),
    scope: Optional[str] = Query(None, description="Filter by metadata scope"),
    limit: int = Query(100, ge=1, le=1000, description="Maximum number of results"),
    offset: int = Query(0, ge=0, description="Number of results to skip"),
    sort_by: Optional[str] = Query(None, description="Field to sort by: 'created_at', 'updated_at', 'id'"),
    order: str = Query("desc", description="Sort order: 'desc' (descending) or 'asc' (ascending)"),
    time_range: Optional[str] = Query(
        None,
        pattern=TIME_RANGE_PATTERN,
        description="Time range filter: 7d, 30d, 90d, or all"
    ),
    api_key: str = Depends(verify_api_key),
    service: MemoryService = Depends(get_memory_service),
):
    """List memories with pagination and sorting"""
    cutoff_date = parse_time_range_cutoff(time_range)
    filters = {"scope": scope} if scope is not None else None

    if cutoff_date is None:
        # Fast path: no time filter — storage handles pagination + sorting natively
        total_count = service.count_memories(
            user_id=user_id,
            agent_id=agent_id,
            run_id=run_id,
            filters=filters,
        )
        memories = service.list_memories(
            user_id=user_id,
            agent_id=agent_id,
            run_id=run_id,
            limit=limit,
            offset=offset,
            sort_by=sort_by,
            order=order,
            filters=filters,
        )
    else:
        # Storage doesn't natively filter by created_at range; pull a wide page,
        # filter, then slice. Total becomes approximate (filtered count, not full set).
        FETCH_CAP = 1000
        raw = service.list_memories(
            user_id=user_id,
            agent_id=agent_id,
            run_id=run_id,
            limit=FETCH_CAP,
            offset=0,
            sort_by=sort_by,
            order=order,
            filters=filters,
        )

        def _created_at(m):
            value = m.get("created_at")
            if isinstance(value, str):
                try:
                    return datetime.fromisoformat(value.replace("Z", "+00:00"))
                except ValueError:
                    return None
            return value

        filtered = [
            m for m in raw
            if (_created_at(m) is not None and _created_at(m) >= cutoff_date)
        ]
        total_count = len(filtered)
        memories = filtered[offset:offset + limit]

    memory_responses = [memory_dict_to_response(m) for m in memories]

    response_data = MemoryListResponse(
        memories=memory_responses,
        total=total_count,
        limit=limit,
        offset=offset,
    )

    return APIResponse(
        success=True,
        data=response_data.model_dump(mode='json'),
        message="Memories retrieved successfully",
    )


@router.get(
    "/stats",
    response_model=APIResponse,
    summary="Get memory statistics",
    description="Get statistics about memories for a user or agent",
)
@limiter.limit(get_rate_limit_string())
async def get_memory_stats(
    request: Request,
    user_id: Optional[str] = Query(None, description="Filter by user ID"),
    agent_id: Optional[str] = Query(None, description="Filter by agent ID"),
    time_range: Optional[str] = Query(
        None,
        pattern="^(7d|30d|90d|all)$",
        description="Time range filter: 7d, 30d, 90d, or all"
    ),
    api_key: str = Depends(verify_api_key),
    service: MemoryService = Depends(get_memory_service),
):
    """Get memory statistics"""
    cutoff_date = parse_time_range_cutoff(time_range)

    stats = service.get_statistics(
        user_id=user_id,
        agent_id=agent_id,
        cutoff_date=cutoff_date,
    )

    return APIResponse(
        success=True,
        data=stats,
        message="Statistics retrieved successfully",
    )


@router.get(
    "/sessions",
    response_model=APIResponse,
    summary="List coding-agent sessions",
    description="Get session summaries projected from stored coding-agent memories",
)
@limiter.limit(get_rate_limit_string())
async def list_memory_sessions(
    request: Request,
    user_id: Optional[str] = Query(None, description="Filter by user ID"),
    agent_id: Optional[str] = Query(None, description="Filter by agent ID"),
    run_id: Optional[str] = Query(None, description="Filter by run ID"),
    limit: int = Query(50, ge=1, le=1000, description="Maximum number of sessions"),
    offset: int = Query(0, ge=0, description="Number of sessions to skip"),
    sort_by: str = Query(
        "last_seen",
        description="Sort field: last_seen, first_seen, event_count, memory_count, run_id",
    ),
    order: str = Query("desc", pattern="^(asc|desc)$", description="Sort order"),
    time_range: Optional[str] = Query(
        None,
        pattern=TIME_RANGE_PATTERN,
        description="Time range filter: 7d, 30d, 90d, or all",
    ),
    api_key: str = Depends(verify_api_key),
    service: MemoryService = Depends(get_memory_service),
):
    """List coding-agent session summaries."""
    cutoff_date = parse_time_range_cutoff(time_range)
    data = service.list_session_summaries(
        user_id=user_id,
        agent_id=agent_id,
        run_id=run_id,
        cutoff_date=cutoff_date,
        limit=limit,
        offset=offset,
        sort_by=sort_by,
        order=order,
    )
    response_data = SessionListResponse(**data)

    return APIResponse(
        success=True,
        data=response_data.model_dump(mode="json", exclude_none=True),
        message="Sessions retrieved successfully",
    )


@router.get(
    "/session-stats",
    response_model=APIResponse,
    summary="Get coding-agent session statistics",
    description="Get overview metrics for coding-agent session timeline views",
)
@limiter.limit(get_rate_limit_string())
async def get_memory_session_stats(
    request: Request,
    user_id: Optional[str] = Query(None, description="Filter by user ID"),
    agent_id: Optional[str] = Query(None, description="Filter by agent ID"),
    run_id: Optional[str] = Query(None, description="Filter by run ID"),
    time_range: Optional[str] = Query(
        None,
        pattern=TIME_RANGE_PATTERN,
        description="Time range filter: 7d, 30d, 90d, or all",
    ),
    api_key: str = Depends(verify_api_key),
    service: MemoryService = Depends(get_memory_service),
):
    """Get coding-agent session statistics."""
    cutoff_date = parse_time_range_cutoff(time_range)
    data = service.get_session_stats(
        user_id=user_id,
        agent_id=agent_id,
        run_id=run_id,
        cutoff_date=cutoff_date,
    )
    response_data = SessionStatsResponse(**data)

    return APIResponse(
        success=True,
        data=response_data.model_dump(mode="json", exclude_none=True),
        message="Session statistics retrieved successfully",
    )


@router.get(
    "/timeline",
    response_model=APIResponse,
    summary="List coding-agent timeline events",
    description="Get cursor-paginated timeline events projected from stored memories",
)
@limiter.limit(get_rate_limit_string())
async def list_memory_timeline(
    request: Request,
    user_id: Optional[str] = Query(None, description="Filter by user ID"),
    agent_id: Optional[str] = Query(None, description="Filter by agent ID"),
    run_id: Optional[str] = Query(None, description="Filter by run ID"),
    event_type: Optional[str] = Query(None, description="Filter by event type"),
    q: Optional[str] = Query(None, description="Search timeline previews and metadata"),
    cursor: Optional[str] = Query(None, description="Opaque cursor from a previous page"),
    limit: int = Query(50, ge=1, le=200, description="Maximum number of events"),
    order: str = Query("desc", pattern="^(asc|desc)$", description="Sort order"),
    time_range: Optional[str] = Query(
        None,
        pattern=TIME_RANGE_PATTERN,
        description="Time range filter: 7d, 30d, 90d, or all",
    ),
    include_source: bool = Query(
        False,
        description="Include full source content instead of previews only",
    ),
    api_key: str = Depends(verify_api_key),
    service: MemoryService = Depends(get_memory_service),
):
    """List coding-agent timeline events."""
    cutoff_date = parse_time_range_cutoff(time_range)
    data = service.list_timeline_events(
        user_id=user_id,
        agent_id=agent_id,
        run_id=run_id,
        event_type=event_type,
        q=q,
        cursor=cursor,
        limit=limit,
        order=order,
        cutoff_date=cutoff_date,
        include_source=include_source,
    )
    response_data = TimelineResponse(**data)

    return APIResponse(
        success=True,
        data=response_data.model_dump(mode="json", exclude_none=True),
        message="Timeline retrieved successfully",
    )


@router.get(
    "/quality",
    response_model=APIResponse,
    summary="Get memory quality metrics",
    description="Analyze memory quality and identify potential issues",
)
@limiter.limit(get_rate_limit_string())
async def get_memory_quality(
    request: Request,
    user_id: Optional[str] = Query(None, description="Filter by user ID"),
    agent_id: Optional[str] = Query(None, description="Filter by agent ID"),
    time_range: Optional[str] = Query(
        None,
        pattern="^(7d|30d|90d|all)$",
        description="Time range filter: 7d, 30d, 90d, or all"
    ),
    api_key: str = Depends(verify_api_key),
    service: MemoryService = Depends(get_memory_service),
):
    """Get memory quality metrics"""
    cutoff_date = parse_time_range_cutoff(time_range)

    quality_metrics = await service.analyze_memory_quality(
        user_id=user_id,
        agent_id=agent_id,
        cutoff_date=cutoff_date,
    )

    return APIResponse(
        success=True,
        data=quality_metrics,
        message="Quality metrics retrieved successfully",
    )


@router.get(
    "/users",
    response_model=APIResponse,
    summary="Get unique users",
    description="Get a list of unique user IDs who have memories stored",
)
@limiter.limit(get_rate_limit_string())
async def get_unique_users(
    request: Request,
    api_key: str = Depends(verify_api_key),
    service: MemoryService = Depends(get_memory_service),
):
    """Get unique users"""
    users = service.get_users()

    return APIResponse(
        success=True,
        data=users,
        message="Users retrieved successfully",
    )


@router.get(
    "/export",
    summary="Export memories",
    description="Export memories to JSON or CSV file",
)
@limiter.limit(get_rate_limit_string())
async def export_memories(
    request: Request,
    format: str = Query("json", description="Export format (json/csv)"),
    user_id: Optional[str] = Query(None, description="Filter by user ID"),
    agent_id: Optional[str] = Query(None, description="Filter by agent ID"),
    run_id: Optional[str] = Query(None, description="Filter by run ID"),
    limit: int = Query(1000, ge=1, le=10000, description="Max memories to export"),
    api_key: str = Depends(verify_api_key),
    service: MemoryService = Depends(get_memory_service),
):
    """Export memories"""
    content = service.memory.export_memories(
        format=format,
        user_id=user_id,
        agent_id=agent_id,
        run_id=run_id,
        limit=limit,
    )

    media_type = "application/json" if format.lower() == "json" else "text/csv"
    filename = f"memories_export.{format.lower()}"

    return Response(
        content=content,
        media_type=media_type,
        headers={"Content-Disposition": f"attachment; filename={filename}"},
    )


@router.get(
    "/{memory_id}",
    response_model=APIResponse,
    summary="Get a memory",
    description="Get a specific memory by ID",
)
@limiter.limit(get_rate_limit_string())
async def get_memory(
    request: Request,
    memory_id: str,
    user_id: Optional[str] = Query(None, description="User ID for access control"),
    agent_id: Optional[str] = Query(None, description="Agent ID for access control"),
    api_key: str = Depends(verify_api_key),
    service: MemoryService = Depends(get_memory_service),
):
    """Get a memory by ID"""
    memory = service.get_memory(
        memory_id=int(memory_id),
        user_id=user_id,
        agent_id=agent_id,
    )
    
    memory_response = memory_dict_to_response(memory)
    
    return APIResponse(
        success=True,
        data=memory_response.model_dump(mode='json'),
        message="Memory retrieved successfully",
    )


@router.put(
    "/batch",
    response_model=APIResponse,
    summary="Batch update memories",
    description="Update multiple memories in a single request (batch operation)",
)
@limiter.limit(get_rate_limit_string())
async def batch_update_memories(
    request: Request,
    body: MemoryBatchUpdateRequest,
    api_key: str = Depends(verify_api_key),
    service: MemoryService = Depends(get_memory_service),
):
    """Update multiple memories in batch"""
    # Convert MemoryUpdateItem objects to dictionaries
    updates_data = [
        {
            "memory_id": item.memory_id,
            "content": item.content,
            "metadata": item.metadata,
        }
        for item in body.updates
    ]
    
    result = service.batch_update_memories(
        updates=updates_data,
        user_id=body.user_id,
        agent_id=body.agent_id,
    )
    
    # Convert updated memories to response format
    updated_memories = []
    for item in result["updated"]:
        try:
            memory = service.get_memory(
                memory_id=item["memory_id"],
                user_id=body.user_id,
                agent_id=body.agent_id,
            )
            updated_memories.append(memory_dict_to_response(memory).model_dump(mode='json'))
        except Exception as e:
            logger.warning(f"Failed to retrieve updated memory {item['memory_id']}: {e}")
            # Include basic info even if full retrieval fails
            updated_memories.append({
                "memory_id": item["memory_id"],
            })
    
    response_data = {
        "memories": updated_memories,
        "total": result["total"],
        "updated_count": result["updated_count"],
        "failed_count": result["failed_count"],
    }
    
    # Only include failed items if there are any
    if result["failed_count"] > 0:
        response_data["failed"] = result["failed"]
    
    return APIResponse(
        success=True,
        data=response_data,
        message=f"Updated {result['updated_count']} out of {result['total']} memories",
    )


@router.put(
    "/{memory_id}",
    response_model=APIResponse,
    summary="Update a memory",
    description="Update an existing memory",
)
@limiter.limit(get_rate_limit_string())
async def update_memory(
    request: Request,
    memory_id: str,
    body: MemoryUpdateRequest,
    user_id: Optional[str] = Query(None, description="User ID for access control"),
    agent_id: Optional[str] = Query(None, description="Agent ID for access control"),
    api_key: str = Depends(verify_api_key),
    service: MemoryService = Depends(get_memory_service),
):
    """Update a memory"""
    # At least one of content or metadata must be provided
    if body.content is None and body.metadata is None:
        from ...models.errors import ErrorCode, APIError
        raise APIError(
            code=ErrorCode.INVALID_REQUEST,
            message="At least one of content or metadata must be provided",
            status_code=400,
        )
    
    result = service.update_memory(
        memory_id=int(memory_id),
        content=body.content,
        user_id=user_id,
        agent_id=agent_id,
        metadata=body.metadata,
    )
    
    memory_response = memory_dict_to_response(result)
    
    return APIResponse(
        success=True,
        data=memory_response.model_dump(mode='json'),
        message="Memory updated successfully",
    )


@router.delete(
    "/batch",
    response_model=APIResponse,
    summary="Bulk delete memories",
    description="Delete multiple memories at once",
)
@limiter.limit(get_rate_limit_string())
async def bulk_delete_memories(
    request: Request,
    body: BulkDeleteRequest,
    api_key: str = Depends(verify_api_key),
    service: MemoryService = Depends(get_memory_service),
):
    """Bulk delete memories"""
    result = service.bulk_delete_memories(
        memory_ids=body.memory_ids,
        user_id=body.user_id,
        agent_id=body.agent_id,
    )
    
    return APIResponse(
        success=True,
        data=result,
        message=f"Deleted {result['deleted_count']} memories",
    )


@router.delete(
    "/{memory_id}",
    response_model=APIResponse,
    summary="Delete a memory",
    description="Delete a specific memory by ID",
)
@limiter.limit(get_rate_limit_string())
async def delete_memory(
    request: Request,
    memory_id: str,
    user_id: Optional[str] = Query(None, description="User ID for access control"),
    agent_id: Optional[str] = Query(None, description="Agent ID for access control"),
    api_key: str = Depends(verify_api_key),
    service: MemoryService = Depends(get_memory_service),
):
    """Delete a memory"""
    service.delete_memory(
        memory_id=int(memory_id),
        user_id=user_id,
        agent_id=agent_id,
    )
    
    return APIResponse(
        success=True,
        data={"memory_id": memory_id},
        message="Memory deleted successfully",
    )


@router.post(
    "/import",
    response_model=APIResponse,
    summary="Import memories",
    description="Import memories from JSON or CSV file",
)
@limiter.limit(get_rate_limit_string())
async def import_memories(
    request: Request,
    file: UploadFile = File(...),
    user_id: Optional[str] = Query(None, description="Override user ID"),
    agent_id: Optional[str] = Query(None, description="Override agent ID"),
    api_key: str = Depends(verify_api_key),
    service: MemoryService = Depends(get_memory_service),
):
    """Import memories"""
    content = (await file.read()).decode("utf-8")

    # Auto-detect format from filename extension
    filename = file.filename or "import.json"
    fmt = "json"
    if filename.lower().endswith(".csv"):
        fmt = "csv"
    elif filename.lower().endswith(".json"):
        fmt = "json"

    result = service.memory.import_memories(
        source=content,
        format=fmt,
        user_id=user_id,
        agent_id=agent_id,
    )

    return APIResponse(
        success=True,
        data=result,
        message=f"Import completed: {result['success']} success, {result['failed']} failed",
    )
