# [DEF:TaskPersistenceModule:Module]
# @SEMANTICS: persistence, sqlite, sqlalchemy, task, storage
# @PURPOSE: Handles the persistence of tasks using SQLAlchemy and the tasks.db database.
# @LAYER: Core
# @RELATION: Used by TaskManager to save and load tasks.
# @INVARIANT: Database schema must match the TaskRecord model structure.

# [SECTION: IMPORTS]
from datetime import datetime
from typing import List, Optional, Dict, Any
import json

from sqlalchemy.orm import Session
from ...models.task import TaskRecord
from ..database import TasksSessionLocal
from .models import Task, TaskStatus, LogEntry
from ..logger import logger, belief_scope
# [/SECTION]

# [DEF:TaskPersistenceService:Class]
# @SEMANTICS: persistence, service, database, sqlalchemy
# @PURPOSE: Provides methods to save and load tasks from the tasks.db database using SQLAlchemy.
class TaskPersistenceService:
    # [DEF:__init__:Function]
    # @PURPOSE: Initializes the persistence service.
    # @PRE:     None.
    # @POST:    Service is ready.
    def __init__(self):
        with belief_scope("TaskPersistenceService.__init__"):
            # We use TasksSessionLocal from database.py
            pass
    # [/DEF:__init__:Function]

    # [DEF:persist_task:Function]
    # @PURPOSE: Persists or updates a single task in the database.
    # @PRE:     isinstance(task, Task)
    # @POST:    Task record created or updated in database.
    # @PARAM:   task (Task) - The task object to persist.
    # @SIDE_EFFECT: Writes to task_records table in tasks.db
    def persist_task(self, task: Task) -> None:
        with belief_scope("TaskPersistenceService.persist_task", f"task_id={task.id}"):
            session: Session = TasksSessionLocal()
            try:
                record = session.query(TaskRecord).filter(TaskRecord.id == task.id).first()
                if not record:
                    record = TaskRecord(id=task.id)
                    session.add(record)
                
                record.type = task.plugin_id
                record.status = task.status.value
                record.environment_id = task.params.get("environment_id") or task.params.get("source_env_id")
                record.started_at = task.started_at
                record.finished_at = task.finished_at
                
                # Ensure params and result are JSON serializable
                def json_serializable(obj):
                    if isinstance(obj, dict):
                        return {k: json_serializable(v) for k, v in obj.items()}
                    elif isinstance(obj, list):
                        return [json_serializable(v) for v in obj]
                    elif isinstance(obj, datetime):
                        return obj.isoformat()
                    return obj

                record.params = json_serializable(task.params)
                record.result = json_serializable(task.result)
                
                # Store logs as JSON, converting datetime to string
                record.logs = []
                for log in task.logs:
                    log_dict = log.dict()
                    if isinstance(log_dict.get('timestamp'), datetime):
                        log_dict['timestamp'] = log_dict['timestamp'].isoformat()
                    # Also clean up any datetimes in context
                    if log_dict.get('context'):
                        log_dict['context'] = json_serializable(log_dict['context'])
                    record.logs.append(log_dict)
                
                # Extract error if failed
                if task.status == TaskStatus.FAILED:
                    for log in reversed(task.logs):
                        if log.level == "ERROR":
                            record.error = log.message
                            break
                
                session.commit()
            except Exception as e:
                session.rollback()
                logger.error(f"Failed to persist task {task.id}: {e}")
            finally:
                session.close()
    # [/DEF:persist_task:Function]

    # [DEF:persist_tasks:Function]
    # @PURPOSE: Persists multiple tasks.
    # @PRE:     isinstance(tasks, list)
    # @POST:    All tasks in list are persisted.
    # @PARAM:   tasks (List[Task]) - The list of tasks to persist.
    def persist_tasks(self, tasks: List[Task]) -> None:
        with belief_scope("TaskPersistenceService.persist_tasks"):
            for task in tasks:
                self.persist_task(task)
    # [/DEF:persist_tasks:Function]

    # [DEF:load_tasks:Function]
    # @PURPOSE: Loads tasks from the database.
    # @PRE:     limit is an integer.
    # @POST:    Returns list of Task objects.
    # @PARAM:   limit (int) - Max tasks to load.
    # @PARAM:   status (Optional[TaskStatus]) - Filter by status.
    # @RETURN:  List[Task] - The loaded tasks.
    def load_tasks(self, limit: int = 100, status: Optional[TaskStatus] = None) -> List[Task]:
        with belief_scope("TaskPersistenceService.load_tasks"):
            session: Session = TasksSessionLocal()
            try:
                query = session.query(TaskRecord)
                if status:
                    query = query.filter(TaskRecord.status == status.value)
                
                records = query.order_by(TaskRecord.created_at.desc()).limit(limit).all()
                
                loaded_tasks = []
                for record in records:
                    try:
                        logs = []
                        if record.logs:
                            for log_data in record.logs:
                                # Handle timestamp conversion if it's a string
                                if isinstance(log_data.get('timestamp'), str):
                                    log_data['timestamp'] = datetime.fromisoformat(log_data['timestamp'])
                                logs.append(LogEntry(**log_data))

                        task = Task(
                            id=record.id,
                            plugin_id=record.type,
                            status=TaskStatus(record.status),
                            started_at=record.started_at,
                            finished_at=record.finished_at,
                            params=record.params or {},
                            result=record.result,
                            logs=logs
                        )
                        loaded_tasks.append(task)
                    except Exception as e:
                        logger.error(f"Failed to reconstruct task {record.id}: {e}")
                
                return loaded_tasks
            finally:
                session.close()
    # [/DEF:load_tasks:Function]

    # [DEF:delete_tasks:Function]
    # @PURPOSE: Deletes specific tasks from the database.
    # @PRE:     task_ids is a list of strings.
    # @POST:    Specified task records deleted from database.
    # @PARAM:   task_ids (List[str]) - List of task IDs to delete.
    def delete_tasks(self, task_ids: List[str]) -> None:
        if not task_ids:
            return
        with belief_scope("TaskPersistenceService.delete_tasks"):
            session: Session = TasksSessionLocal()
            try:
                session.query(TaskRecord).filter(TaskRecord.id.in_(task_ids)).delete(synchronize_session=False)
                session.commit()
            except Exception as e:
                session.rollback()
                logger.error(f"Failed to delete tasks: {e}")
            finally:
                session.close()
    # [/DEF:delete_tasks:Function]

# [/DEF:TaskPersistenceService:Class]
# [/DEF:TaskPersistenceModule:Module]