# #region Test.Integration.TaskManager [C:4] [TYPE Module] [SEMANTICS test,integration,task,postgres,testcontainers]
# @BRIEF Integration tests for Task models (TaskRecord, TaskLogRecord) — JSONB params/result,
#         FK CASCADE/SET NULL, ILIKE search, composite indexes.
# @RELATION BINDS_TO -> [TaskModels]
# @RELATION DEPENDS_ON -> [IntegrationTestConftest]
# @PRE Docker daemon running; testcontainers PostgresContainer can start.
# @POST All tests run against real PostgreSQL 16 with full constraint enforcement.
# @TEST_EDGE: task_record_crud -> Create, read, update, delete TaskRecord
# @TEST_EDGE: task_jsonb_params -> JSONB params and result persist correctly
# @TEST_EDGE: task_log_cascade -> Deleting task cascades to log records
# @TEST_EDGE: task_log_ilike -> ILIKE search on log message works
# @TEST_EDGE: task_environment_set_null -> Deleting environment sets FK to NULL
# @TEST_EDGE: task_status_filter -> Filtering tasks by status works
# @TEST_EDGE: task_composite_index_query -> Queries using composite indexes are efficient
# @TEST_EDGE: task_log_ordered_query -> Logs can be ordered by timestamp

import pytest
from datetime import UTC, datetime, timedelta

from sqlalchemy import text
from sqlalchemy.exc import IntegrityError

from src.models.task import TaskRecord, TaskLogRecord


class TestTaskRecord:
    """TaskRecord — CRUD, JSONB, FK SET NULL."""

    # #region test_task_crud [C:2] [TYPE Function]
    def test_task_crud(self, db_session):
        """Full create/read/update/delete cycle for TaskRecord."""
        task = TaskRecord(
            type="migration",
            status="PENDING",
            params={"source_env": "prod", "target_env": "staging", "dashboard_ids": [1, 2, 3]},
        )
        db_session.add(task)
        db_session.commit()

        # Read
        fetched = db_session.query(TaskRecord).filter_by(type="migration").first()
        assert fetched is not None
        assert fetched.status == "PENDING"
        assert fetched.params["source_env"] == "prod"

        # Update
        fetched.status = "RUNNING"
        fetched.started_at = datetime.now(UTC)
        db_session.commit()

        updated = db_session.query(TaskRecord).filter_by(id=fetched.id).first()
        assert updated.status == "RUNNING"
        assert updated.started_at is not None
    # #endregion test_task_crud

    # #region test_task_jsonb_result [C:2] [TYPE Function]
    def test_task_jsonb_result(self, db_session):
        """JSONB result with nested data persists correctly."""
        task = TaskRecord(
            type="backup",
            status="SUCCESS",
            params={"env": "prod"},
            result={
                "dashboards_count": 5,
                "files": ["dash1.yaml", "dash2.yaml"],
                "size_bytes": 1024000,
                "checksum": {"algorithm": "sha256", "value": "abc123"},
            },
        )
        db_session.add(task)
        db_session.commit()

        fetched = db_session.query(TaskRecord).filter_by(type="backup").first()
        assert fetched.result["dashboards_count"] == 5
        assert fetched.result["checksum"]["algorithm"] == "sha256"
        assert len(fetched.result["files"]) == 2
    # #endregion test_task_jsonb_result

    # #region test_task_error_field [C:2] [TYPE Function]
    def test_task_error_field(self, db_session):
        """Error field stores failure details."""
        task = TaskRecord(
            type="migration",
            status="FAILED",
            error="Connection timeout after 30s",
        )
        db_session.add(task)
        db_session.commit()

        fetched = db_session.query(TaskRecord).filter_by(status="FAILED").first()
        assert "Connection timeout" in fetched.error
    # #endregion test_task_error_field

    # #region test_task_status_filter [C:2] [TYPE Function]
    def test_task_status_filter(self, db_session):
        """Filter tasks by status."""
        db_session.add_all([
            TaskRecord(type="backup", status="SUCCESS"),
            TaskRecord(type="backup", status="FAILED"),
            TaskRecord(type="migration", status="RUNNING"),
            TaskRecord(type="migration", status="PENDING"),
        ])
        db_session.commit()

        running = db_session.query(TaskRecord).filter_by(status="RUNNING").all()
        assert len(running) == 1

        completed = db_session.query(TaskRecord).filter(
            TaskRecord.status.in_(["SUCCESS", "FAILED"])
        ).all()
        assert len(completed) == 2
    # #endregion test_task_status_filter


class TestTaskLogRecord:
    """TaskLogRecord — FK cascade, ILIKE, ordering."""

    # #region test_task_log_create [C:2] [TYPE Function]
    def test_task_log_create(self, db_session):
        """Create log entries for a task."""
        task = TaskRecord(type="migration", status="RUNNING")
        db_session.add(task)
        db_session.commit()

        now = datetime.now(UTC)
        db_session.add_all([
            TaskLogRecord(task_id=task.id, timestamp=now, level="INFO", source="system",
                          message="Task started"),
            TaskLogRecord(task_id=task.id, timestamp=now + timedelta(seconds=1),
                          level="INFO", source="plugin", message="Processing dashboard 1"),
            TaskLogRecord(task_id=task.id, timestamp=now + timedelta(seconds=2),
                          level="ERROR", source="superset_api", message="API timeout"),
        ])
        db_session.commit()

        logs = db_session.query(TaskLogRecord).filter_by(task_id=task.id).all()
        assert len(logs) == 3
    # #endregion test_task_log_create

    # #region test_task_log_cascade_delete [C:2] [TYPE Function]
    def test_task_log_cascade_delete(self, db_session):
        """Deleting a task cascades to its log records."""
        task = TaskRecord(type="migration", status="SUCCESS")
        db_session.add(task)
        db_session.commit()

        db_session.add(TaskLogRecord(
            task_id=task.id, timestamp=datetime.now(UTC),
            level="INFO", source="system", message="Log entry",
        ))
        db_session.commit()

        assert db_session.query(TaskLogRecord).filter_by(task_id=task.id).count() == 1

        db_session.delete(task)
        db_session.commit()

        assert db_session.query(TaskLogRecord).filter_by(task_id=task.id).count() == 0
    # #endregion test_task_log_cascade_delete

    # #region test_task_log_ilike_search [C:2] [TYPE Function]
    def test_task_log_ilike_search(self, db_session):
        """ILIKE search on message field (PostgreSQL-specific)."""
        task = TaskRecord(type="migration", status="RUNNING")
        db_session.add(task)
        db_session.commit()

        now = datetime.now(UTC)
        db_session.add_all([
            TaskLogRecord(task_id=task.id, timestamp=now, level="INFO", source="system",
                          message="Task completed successfully"),
            TaskLogRecord(task_id=task.id, timestamp=now, level="ERROR", source="system",
                          message="Task failed with timeout"),
            TaskLogRecord(task_id=task.id, timestamp=now, level="INFO", source="plugin",
                          message="Processing dashboard Sales Report"),
        ])
        db_session.commit()

        # Search by keyword
        result = db_session.query(TaskLogRecord).filter(
            TaskLogRecord.message.ilike("%completed%")
        ).all()
        assert len(result) == 1
        assert "completed" in result[0].message

        # Case-insensitive search
        result = db_session.query(TaskLogRecord).filter(
            TaskLogRecord.message.ilike("%SALES%")
        ).all()
        assert len(result) == 1

        # Multiple matches
        result = db_session.query(TaskLogRecord).filter(
            TaskLogRecord.message.ilike("%task%")
        ).all()
        assert len(result) == 2
    # #endregion test_task_log_ilike_search

    # #region test_task_log_ordered_query [C:2] [TYPE Function]
    def test_task_log_ordered_query(self, db_session):
        """Logs can be ordered by timestamp with composite index."""
        task = TaskRecord(type="migration", status="RUNNING")
        db_session.add(task)
        db_session.commit()

        base = datetime.now(UTC)
        for i in range(5):
            db_session.add(TaskLogRecord(
                task_id=task.id, timestamp=base + timedelta(minutes=i),
                level="INFO", source="system",
                message=f"Step {i+1}",
            ))
        db_session.commit()

        logs = db_session.query(TaskLogRecord).filter_by(task_id=task.id)\
            .order_by(TaskLogRecord.timestamp.asc()).all()
        assert logs[0].message == "Step 1"
        assert logs[-1].message == "Step 5"

        # Descending
        logs_desc = db_session.query(TaskLogRecord).filter_by(task_id=task.id)\
            .order_by(TaskLogRecord.timestamp.desc()).all()
        assert logs_desc[0].message == "Step 5"
    # #endregion test_task_log_ordered_query

    # #region test_task_log_source_filter [C:2] [TYPE Function]
    def test_task_log_source_filter(self, db_session):
        """Filter logs by source."""
        task = TaskRecord(type="migration", status="RUNNING")
        db_session.add(task)
        db_session.commit()

        now = datetime.now(UTC)
        db_session.add_all([
            TaskLogRecord(task_id=task.id, timestamp=now, level="INFO", source="system",
                          message="System log"),
            TaskLogRecord(task_id=task.id, timestamp=now, level="INFO", source="plugin",
                          message="Plugin log"),
            TaskLogRecord(task_id=task.id, timestamp=now, level="INFO", source="superset_api",
                          message="API log"),
        ])
        db_session.commit()

        plugin_logs = db_session.query(TaskLogRecord).filter(
            TaskLogRecord.task_id == task.id,
            TaskLogRecord.source == "plugin",
        ).all()
        assert len(plugin_logs) == 1
        assert plugin_logs[0].message == "Plugin log"
    # #endregion test_task_log_source_filter


class TestTaskEnvironmentFK:
    """TaskRecord FK to environments — SET NULL behavior."""

    # #region test_task_without_environment [C:2] [TYPE Function]
    def test_task_without_environment(self, db_session):
        """Task can be created without environment_id (nullable FK)."""
        task = TaskRecord(type="local", status="SUCCESS")
        db_session.add(task)
        db_session.commit()
        assert task.environment_id is None
    # #endregion test_task_without_environment


class TestTaskTimestampDefaults:
    """TaskRecord server_default and auto-timestamps."""

    # #region test_task_created_at_default [C:2] [TYPE Function]
    def test_task_created_at_default(self, db_session):
        """created_at is auto-set by server_default."""
        task = TaskRecord(type="test", status="PENDING")
        db_session.add(task)
        db_session.commit()
        assert task.created_at is not None
    # #endregion test_task_created_at_default

    # #region test_task_log_server_default [C:2] [TYPE Function]
    def test_task_log_server_default(self, db_session):
        """TaskLogRecord without explicit timestamp gets server_default."""
        task = TaskRecord(type="test", status="RUNNING")
        db_session.add(task)
        db_session.commit()

        from datetime import UTC, datetime
        now = datetime.now(UTC)
        log = TaskLogRecord(
            task_id=task.id,
            timestamp=now,
            level="INFO",
            source="system",
            message="Explicit timestamp",
        )
        db_session.add(log)
        db_session.commit()
        assert log.timestamp is not None
        assert log.timestamp == now
    # #endregion test_task_log_server_default
# #endregion Test.Integration.TaskManager
