"""SQLite test result parsing."""

from __future__ import annotations

import logging
import sqlite3
from typing import TYPE_CHECKING

from .._model import VerificationType
from ..test_discovery.models import TestType
from ._path_resolution import file_path_from_module_name
from .models import FunctionTestInvocation, InvocationId, TestResults

if TYPE_CHECKING:
    from pathlib import Path

    from .models import TestConfig, TestFiles

log = logging.getLogger(__name__)


def parse_sqlite_test_results(
    sqlite_file_path: Path,
    test_files: TestFiles,
    test_config: TestConfig,
) -> TestResults:
    """Parse test results from a SQLite database."""
    test_results = TestResults()
    if not sqlite_file_path.exists():
        log.warning(
            "No test results for %s found.",
            sqlite_file_path,
        )
        return test_results

    db: sqlite3.Connection | None = None
    try:
        db = sqlite3.connect(sqlite_file_path)
        cur = db.cursor()
        data = cur.execute(
            "SELECT test_module_path, test_class_name,"
            " test_function_name,"
            " function_getting_tested, loop_index,"
            " iteration_id, runtime,"
            " return_value, verification_type,"
            " cpu_runtime"
            " FROM test_results"
        ).fetchall()
    except sqlite3.Error:
        log.warning(
            "Failed to read test results from %s.",
            sqlite_file_path,
            exc_info=True,
        )
        if db is not None:
            db.close()
        return test_results
    except OSError:
        log.warning(
            "Failed to open %s.",
            sqlite_file_path,
            exc_info=True,
        )
        return test_results
    finally:
        if db is not None:
            db.close()

    for val in data:
        _process_sqlite_row(val, test_files, test_config, test_results)

    return test_results


def _process_sqlite_row(
    val: tuple[object, ...],
    test_files: TestFiles,
    test_config: TestConfig,
    test_results: TestResults,
) -> None:
    """Process a single row from the sqlite table."""
    try:
        _process_sqlite_row_inner(val, test_files, test_config, test_results)
    except Exception:
        log.exception("Failed to parse sqlite test result")


def _process_sqlite_row_inner(
    val: tuple[object, ...],
    test_files: TestFiles,
    test_config: TestConfig,
    test_results: TestResults,
) -> None:
    """Inner processing for a single sqlite row."""
    test_module_path = val[0]
    test_class_name = val[1] or None
    test_function_name = val[2] or None
    function_getting_tested = val[3]
    loop_index = val[4]
    iteration_id = val[5]
    runtime = val[6]
    verification_type = val[8]
    cpu_runtime = val[9]

    test_file_path = file_path_from_module_name(
        test_module_path,  # type: ignore[arg-type]
        test_config.tests_project_rootdir,
    )

    if verification_type in {
        VerificationType.INIT_STATE_FTO,
        VerificationType.INIT_STATE_HELPER,
    }:
        test_type: TestType = TestType.INIT_STATE_TEST
    else:
        found = test_files.get_test_type_by_original_file_path(
            test_file_path,
        )
        if found is None:
            found = test_files.get_test_type_by_instrumented_file_path(
                test_file_path,
            )
        if found is None:
            log.debug(
                "Skipping result for %s: could not determine test type",
                test_function_name,
            )
            return
        test_type = found

    ret_val = None
    if loop_index == 1 and val[7]:
        import dill as pickle  # noqa: PLC0415

        try:
            ret_val = (pickle.loads(val[7]),)  # noqa: S301
        except (pickle.UnpicklingError, EOFError, ValueError, TypeError):
            log.warning(
                "Failed to deserialize return value for %s",
                test_function_name,
                exc_info=True,
            )
            return

    test_results.add(
        FunctionTestInvocation(
            loop_index=loop_index,  # type: ignore[arg-type]
            id=InvocationId(
                test_module_path=test_module_path,  # type: ignore[arg-type]
                test_class_name=test_class_name,  # type: ignore[arg-type]
                test_function_name=test_function_name,  # type: ignore[arg-type]
                function_getting_tested=function_getting_tested,  # type: ignore[arg-type]
                iteration_id=iteration_id,  # type: ignore[arg-type]
            ),
            file_name=test_file_path,
            did_pass=True,
            runtime=runtime,  # type: ignore[arg-type]
            test_framework=test_config.test_framework,
            test_type=test_type,
            return_value=ret_val,
            cpu_runtime=cpu_runtime,  # type: ignore[arg-type]
            timed_out=False,
            verification_type=(
                VerificationType(verification_type)
                if verification_type
                else None
            ),
        ),
    )
