"""
Engine Bridge for Async GUI
===========================
Provides async wrappers around the synchronous engine functions.
"""

import asyncio
import logging
from concurrent.futures import ThreadPoolExecutor
from typing import Callable, Awaitable, Optional

from fmatch.core import DedupeConfig, MatchConfig, determine_optimal_blocking_strategy
from fmatch.core.engine import (
    process_dataframe,
    BlockingMode,
)
from fmatch.desktop.models import (
    BlockingSuggestionModel,
    DuplicateResultModel,
    MatchResultModel,
    ProgressUpdate,
)

import pandas as pd

# Use a single thread pool executor to avoid creating new threads for each call
executor = ThreadPoolExecutor(max_workers=1)
log = logging.getLogger(__name__)


async def suggest_blocking(
    src_df: pd.DataFrame,
    ref_df: Optional[pd.DataFrame] = None,
    mappings: list = None,
    mode: str = "dedupe",
    ruleset: str = "default",
) -> BlockingSuggestionModel:
    """
    Async wrapper for the core blocking suggestion function.
    """
    loop = asyncio.get_running_loop()

    # Convert mode string to BlockingMode enum
    blocking_mode = BlockingMode.DEDUPE if mode == "duplicate" else BlockingMode.MATCH

    result = await loop.run_in_executor(
        executor,
        determine_optimal_blocking_strategy,
        src_df,
        ref_df,
        mappings,
        blocking_mode,
        ruleset,
    )

    # Extract key metrics for the model
    return BlockingSuggestionModel(
        column=result.get("source_block_col", ""),
        blocked_pairs=result.get("blocked_pairs", 0),
        total_pairs=result.get("total_pairs", 0),
    )


async def dedupe(
    cfg: DedupeConfig,
    progress_callback: Optional[Callable[[ProgressUpdate], Awaitable[None]]] = None,
) -> DuplicateResultModel:
    """
    Async wrapper for deduplication with progress reporting.
    """
    loop = asyncio.get_running_loop()

    # Load the data
    if isinstance(cfg.input_path, pd.DataFrame):
        input_df = cfg.input_path
    else:
        input_df = pd.read_csv(cfg.input_path)

    # Track progress
    total_rows = len(input_df)
    processed_rows = 0

    async def report_progress(message: str, progress_pct: float):
        if progress_callback:
            update = ProgressUpdate(
                progress=progress_pct / 100.0,  # Convert to 0-1 range
                message=message,
                items_processed=processed_rows,
                total_items=total_rows,
            )
            await progress_callback(update)

    # Report start
    await report_progress("Starting deduplication...", 0)

    # Run the actual processing in the thread pool
    import time

    start_time = time.time()

    try:
        # Apply blocking if configured to do so
        if cfg.apply_blocking:
            from fmatch.core.engine import make_blocking_column

            input_df_processed = make_blocking_column(
                df=input_df, cfg=cfg, df_type="source"
            )
        else:
            input_df_processed = input_df

        result_df = await loop.run_in_executor(
            executor,
            process_dataframe,
            input_df_processed,  # input_df
            None,  # ref_df (None for dedupe)
            cfg,  # config
            cfg.rec_id_col,  # rec_id_col
            None,  # src_id_col
            None,  # ref_id_col
            None,  # progress_q (we'll handle progress differently)
            None,  # run_stats
        )

        # Convert results to expected format
        matches = []
        if result_df is not None and not result_df.empty:
            matches = result_df.to_dict("records")

        total_duration = time.time() - start_time

        # Report completion
        await report_progress("Deduplication complete", 100)

        return DuplicateResultModel(matches=matches, total_duration=total_duration)

    except Exception as e:
        log.error(f"Error in dedupe bridge: {e}", exc_info=True)
        await report_progress(f"Error: {str(e)}", 0)
        return DuplicateResultModel(matches=[], total_duration=0.0)


async def match(
    cfg: MatchConfig,
    progress_callback: Optional[Callable[[ProgressUpdate], Awaitable[None]]] = None,
) -> MatchResultModel:
    """
    Async wrapper for matching with progress reporting.
    """
    loop = asyncio.get_running_loop()

    # Load the data
    if isinstance(cfg.src_path, pd.DataFrame):
        src_df = cfg.src_path
    else:
        src_df = pd.read_csv(cfg.src_path)

    if isinstance(cfg.ref_path, pd.DataFrame):
        ref_df = cfg.ref_path
    else:
        ref_df = pd.read_csv(cfg.ref_path)

    # Track progress
    total_rows = len(src_df)
    processed_rows = 0

    async def report_progress(message: str, progress_pct: float):
        if progress_callback:
            update = ProgressUpdate(
                progress=progress_pct / 100.0,  # Convert to 0-1 range
                message=message,
                items_processed=processed_rows,
                total_items=total_rows,
            )
            await progress_callback(update)

    # Report start
    await report_progress("Starting matching...", 0)

    # Run the actual processing in the thread pool
    import time

    start_time = time.time()

    try:
        # Apply blocking if configured to do so
        if cfg.apply_blocking:
            from fmatch.core.engine import make_blocking_column

            src_df_processed = make_blocking_column(
                df=src_df, cfg=cfg, df_type="source"
            )
            ref_df_processed = make_blocking_column(
                df=ref_df, cfg=cfg, df_type="reference"
            )
        else:
            src_df_processed = src_df
            ref_df_processed = ref_df

        result_df = await loop.run_in_executor(
            executor,
            process_dataframe,
            src_df_processed,  # input_df (source for match)
            ref_df_processed,  # ref_df
            cfg,  # config
            None,  # rec_id_col (not used for match)
            cfg.src_id_col,  # src_id_col
            cfg.ref_id_col,  # ref_id_col
            None,  # progress_q (we'll handle progress differently)
            None,  # run_stats
        )

        # Convert results to expected format
        matches = []
        if result_df is not None and not result_df.empty:
            matches = result_df.to_dict("records")

        total_duration = time.time() - start_time

        # Report completion
        await report_progress("Matching complete", 100)

        return MatchResultModel(matches=matches, total_duration=total_duration)

    except Exception as e:
        log.error(f"Error in match bridge: {e}", exc_info=True)
        await report_progress(f"Error: {str(e)}", 0)
        return MatchResultModel(matches=[], total_duration=0.0)


def shutdown_executor():
    """
    Cleanly shut down the thread pool executor.
    To be called when the application exits.
    """
    executor.shutdown(wait=True)
