"""Shard planner — splits long audio jobs into segments for distributed processing."""

from __future__ import annotations

import uuid

import structlog

from hive_compute.config import get_settings
from hive_compute.coordinator.database import get_db_ctx, record_event

logger = structlog.get_logger()

# Hardcoded floor — configurable values come from Settings
MIN_SHARD_LENGTH = 15.0


def plan_shards(
    job_id: str,
    audio_duration: float,
    shard_length: float | None = None,
    overlap: float | None = None,
) -> list[dict]:
    """Generate shard plan for a job based on audio duration.

    Returns list of shard dicts with start/end/overlap info.
    Short audio (< shard_length + MIN_SHARD_LENGTH) gets a single shard.
    """
    if audio_duration <= 0:
        raise ValueError(f"Invalid audio duration: {audio_duration}")

    settings = get_settings()
    if shard_length is None:
        shard_length = settings.validated_shard_length()
    if overlap is None:
        overlap = max(0.0, settings.shard_overlap_seconds)
    max_shard = settings.validated_shard_max()

    shard_length = max(1.0, min(shard_length, max_shard))
    # Overlap must be less than shard length to prevent duplicate audio windows
    overlap = max(0.0, min(overlap, shard_length * 0.5))

    shards = []

    # Short audio → single shard, no splitting (but respect hard max)
    if audio_duration <= shard_length + MIN_SHARD_LENGTH and audio_duration <= max_shard:
        shards.append({
            "shard_id": f"shard_{uuid.uuid4().hex[:12]}",
            "job_id": job_id,
            "shard_index": 0,
            "start_seconds": 0.0,
            "end_seconds": audio_duration,
            "overlap_seconds": 0.0,
        })
        return shards

    # Split into overlapping segments
    start = 0.0
    index = 0
    while start < audio_duration:
        end = min(start + shard_length, audio_duration)

        # Don't create tiny trailing shards — extend the previous one,
        # but only if the result stays under the hard max.
        remaining = audio_duration - end
        if 0 < remaining < MIN_SHARD_LENGTH:
            actual_overlap = overlap if start > 0 else 0.0
            extended = audio_duration - max(0.0, start - actual_overlap)
            if extended <= max_shard:
                end = audio_duration

        actual_overlap = overlap if start > 0 else 0.0

        shards.append({
            "shard_id": f"shard_{uuid.uuid4().hex[:12]}",
            "job_id": job_id,
            "shard_index": index,
            "start_seconds": max(0.0, start - actual_overlap),
            "end_seconds": end,
            "overlap_seconds": actual_overlap,
        })

        start = end
        index += 1

        if end >= audio_duration:
            break

    return shards


def create_shards_for_job(job_id: str, audio_duration: float, db=None) -> int:
    """Plan and write shards to the database. Returns shard count.

    If db is provided, writes within the caller's transaction (atomic with job insert).
    Otherwise opens its own transaction.
    """
    shard_plan = plan_shards(job_id, audio_duration)

    if db is not None:
        return _write_shards(db, job_id, audio_duration, shard_plan)

    with get_db_ctx() as db:
        return _write_shards(db, job_id, audio_duration, shard_plan)


def _write_shards(db, job_id: str, audio_duration: float, shard_plan: list[dict]) -> int:
    """Write shard rows and update job within an existing transaction."""
    max_attempts = get_settings().validated_fallback_max_attempts()

    for shard in shard_plan:
        db.execute(
            """
            INSERT INTO shards (
                shard_id, job_id, shard_index, start_seconds, end_seconds, overlap_seconds, max_attempts
            )
            VALUES (?, ?, ?, ?, ?, ?, ?)
            """,
            (
                shard["shard_id"],
                shard["job_id"],
                shard["shard_index"],
                shard["start_seconds"],
                shard["end_seconds"],
                shard["overlap_seconds"],
                max_attempts,
            ),
        )

    db.execute(
        "UPDATE jobs SET shard_count = ?, audio_duration = ? WHERE job_id = ?",
        (len(shard_plan), audio_duration, job_id),
    )

    record_event(
        db, job_id, "shards_planned",
        to_status="pending",
        actor="planner",
        detail=f"{len(shard_plan)} shards, {audio_duration:.1f}s total",
    )

    logger.info("Shards planned", job_id=job_id, shard_count=len(shard_plan), duration=audio_duration)
    return len(shard_plan)
