from __future__ import annotations

from collections.abc import Iterable, Mapping
import logging
from pathlib import Path
from typing import TypeAlias

from ..utils.record_tools import split_record
from .writer import FieldValue, InfluxWriter, WriteRecord

logger = logging.getLogger(__name__)


IndexedWriteRecord: TypeAlias = tuple[
    int,
    Mapping[str, object],
    Mapping[str, str] | None,
    Mapping[str, FieldValue] | None,
]


def _error_detail(index: int, error: Exception) -> dict[str, object]:
    return {"record_index": index, "error": str(error)}


def _flush_chunk(
    *,
    writer: InfluxWriter,
    chunk: list[IndexedWriteRecord],
    continue_on_error: bool,
    errors: list[dict[str, object]] | None,
) -> tuple[int, int]:
    write_records_input: list[WriteRecord] = [
        (record, extra_tags, extra_fields)
        for _, record, extra_tags, extra_fields in chunk
    ]
    try:
        writer.write_records(write_records_input)
        return len(chunk), 0
    except Exception as error:
        if not continue_on_error:
            raise

        # Avoid duplicate risk: do not per-record retry after uncertain API failures.
        # Safe fallback is only for local validation errors (raised before API write).
        if not isinstance(error, ValueError):
            records_failed = len(chunk)
            if errors is not None:
                for index, _, _, _ in chunk:
                    errors.append(_error_detail(index, error))
            logger.exception(
                "Chunk write failed; marking %d records failed without retry to avoid duplicates.",
                records_failed,
            )
            return 0, records_failed

    records_written = 0
    records_failed = 0
    for index, record, extra_tags, extra_fields in chunk:
        try:
            writer.write_record(
                record,
                extra_tags=extra_tags,
                extra_fields=extra_fields,
            )
            records_written += 1
        except Exception as error:
            records_failed += 1
            logger.exception("Failed writing record #%d.", index)
            if errors is not None:
                errors.append(_error_detail(index, error))
    return records_written, records_failed


def write_batch(
    records: Iterable[Mapping[str, object]],
    *,
    settings_path: str | Path | None = None,
    continue_on_error: bool = False,
    chunk_size: int = 500,
    return_errors: bool = False,
) -> dict[str, object]:
    """Write multiple QOI records.

    Parameters
    ----------
    records : Iterable[Mapping[str, object]]
        Record dictionaries using standard keys and optional
        ``extra_tags`` / ``extra_fields``.
    settings_path : str | Path | None, default=None
        Optional path to settings TOML.
    continue_on_error : bool, default=False
        If ``True``, continue writing after per-record failures.
    chunk_size : int, default=500
        Number of records per write request.
    return_errors : bool, default=False
        If ``True``, include a per-record ``errors`` list in the summary.

    Returns
    -------
    dict[str, object]
        Summary counters: ``records_total``, ``records_written``,
        and ``records_failed``. If ``return_errors`` is ``True``, includes
        ``errors`` as ``list[dict[str, object]]``.
    """
    if chunk_size < 1:
        raise ValueError("'chunk_size' must be >= 1.")

    logger.info("write_batch: starting (chunk_size=%d).", chunk_size)
    records_total = 0
    records_written = 0
    records_failed = 0
    errors: list[dict[str, object]] | None = [] if return_errors else None
    pending_chunk: list[IndexedWriteRecord] = []

    with InfluxWriter.from_toml(settings_path) as writer:
        for raw_record in records:
            records_total += 1
            try:
                record, extra_tags, extra_fields = split_record(raw_record)
            except Exception as error:
                records_failed += 1
                logger.exception("Failed preparing record #%d.", records_total)
                if errors is not None:
                    errors.append(_error_detail(records_total, error))
                if not continue_on_error:
                    raise
                continue

            pending_chunk.append((records_total, record, extra_tags, extra_fields))
            if len(pending_chunk) >= chunk_size:
                written, failed = _flush_chunk(
                    writer=writer,
                    chunk=pending_chunk,
                    continue_on_error=continue_on_error,
                    errors=errors,
                )
                records_written += written
                records_failed += failed
                pending_chunk = []

        if pending_chunk:
            written, failed = _flush_chunk(
                writer=writer,
                chunk=pending_chunk,
                continue_on_error=continue_on_error,
                errors=errors,
            )
            records_written += written
            records_failed += failed

    if records_failed:
        logger.warning(
            "write_batch: done — %d written, %d failed (of %d total).",
            records_written,
            records_failed,
            records_total,
        )
    else:
        logger.info(
            "write_batch: done — %d/%d records written.",
            records_written,
            records_total,
        )
    summary: dict[str, object] = {
        "records_total": records_total,
        "records_written": records_written,
        "records_failed": records_failed,
    }
    if errors is not None:
        summary["errors"] = errors
    return summary
