"Public API for programmatic use of kinemotion analysis."

import json
import time
from collections.abc import Callable
from dataclasses import dataclass
from pathlib import Path

from .cmj.analysis import detect_cmj_phases
from .cmj.debug_overlay import CMJDebugOverlayRenderer
from .cmj.kinematics import CMJMetrics, calculate_cmj_metrics
from .cmj.metrics_validator import CMJMetricsValidator
from .core.auto_tuning import (
    analyze_video_sample,
    auto_tune_parameters,
)
from .core.filtering import reject_outliers
from .core.metadata import (
    AlgorithmConfig,
    DetectionConfig,
    DropDetectionConfig,
    ProcessingInfo,
    ResultMetadata,
    SmoothingConfig,
    VideoInfo,
    create_timestamp,
    get_kinemotion_version,
)
from .core.pipeline_utils import (
    apply_expert_overrides,
    apply_smoothing,
    convert_timer_to_stage_names,
    determine_confidence_levels,
    extract_vertical_positions,
    parse_quality_preset,
    print_verbose_parameters,
    process_all_frames,
    process_videos_bulk_generic,
)
from .core.pose import PoseTracker
from .core.quality import assess_jump_quality
from .core.timing import PerformanceTimer
from .core.video_io import VideoProcessor
from .dropjump.analysis import (
    detect_ground_contact,
    find_contact_phases,
)
from .dropjump.debug_overlay import DebugOverlayRenderer
from .dropjump.kinematics import DropJumpMetrics, calculate_drop_jump_metrics
from .dropjump.metrics_validator import DropJumpMetricsValidator


@dataclass
class DropJumpVideoResult:
    """Result of processing a single drop jump video."""

    video_path: str
    success: bool
    metrics: DropJumpMetrics | None = None
    error: str | None = None
    processing_time: float = 0.0


@dataclass
class DropJumpVideoConfig:
    """Configuration for processing a single drop jump video."""

    video_path: str
    quality: str = "balanced"
    output_video: str | None = None
    json_output: str | None = None
    drop_start_frame: int | None = None
    smoothing_window: int | None = None
    velocity_threshold: float | None = None
    min_contact_frames: int | None = None
    visibility_threshold: float | None = None
    detection_confidence: float | None = None
    tracking_confidence: float | None = None


def process_dropjump_video(
    video_path: str,
    quality: str = "balanced",
    output_video: str | None = None,
    json_output: str | None = None,
    drop_start_frame: int | None = None,
    smoothing_window: int | None = None,
    velocity_threshold: float | None = None,
    min_contact_frames: int | None = None,
    visibility_threshold: float | None = None,
    detection_confidence: float | None = None,
    tracking_confidence: float | None = None,
    verbose: bool = False,
    timer: PerformanceTimer | None = None,
    pose_tracker: "PoseTracker | None" = None,
) -> DropJumpMetrics:
    """
    Process a single drop jump video and return metrics.

    Jump height is calculated from flight time using kinematic formula (h = g*t²/8).

    Args:
        video_path: Path to the input video file
        quality: Analysis quality preset ("fast", "balanced", or "accurate")
        output_video: Optional path for debug video output
        json_output: Optional path for JSON metrics output
        drop_start_frame: Optional manual drop start frame
        smoothing_window: Optional override for smoothing window
        velocity_threshold: Optional override for velocity threshold
        min_contact_frames: Optional override for minimum contact frames
        visibility_threshold: Optional override for visibility threshold
        detection_confidence: Optional override for pose detection confidence
        tracking_confidence: Optional override for pose tracking confidence
        verbose: Print processing details
        timer: Optional PerformanceTimer for measuring operations
        pose_tracker: Optional pre-initialized PoseTracker instance (reused if provided)

    Returns:
        DropJumpMetrics object containing analysis results

    Raises:
        ValueError: If video cannot be processed or parameters are invalid
        FileNotFoundError: If video file does not exist
    """
    if not Path(video_path).exists():
        raise FileNotFoundError(f"Video file not found: {video_path}")

    from .core.determinism import set_deterministic_mode

    set_deterministic_mode(seed=42)

    start_time = time.time()
    if timer is None:
        timer = PerformanceTimer()

    quality_preset = parse_quality_preset(quality)

    with timer.measure("video_initialization"):
        with VideoProcessor(video_path, timer=timer) as video:
            detection_conf, tracking_conf = determine_confidence_levels(
                quality_preset, detection_confidence, tracking_confidence
            )

            if verbose:
                print("Processing all frames with MediaPipe pose tracking...")

            tracker = pose_tracker
            should_close_tracker = False

            if tracker is None:
                tracker = PoseTracker(
                    min_detection_confidence=detection_conf,
                    min_tracking_confidence=tracking_conf,
                    timer=timer,
                )
                should_close_tracker = True

            frames, landmarks_sequence, frame_indices = process_all_frames(
                video, tracker, verbose, timer, close_tracker=should_close_tracker
            )

            with timer.measure("parameter_auto_tuning"):
                characteristics = analyze_video_sample(
                    landmarks_sequence, video.fps, video.frame_count
                )
                params = auto_tune_parameters(characteristics, quality_preset)

                params = apply_expert_overrides(
                    params,
                    smoothing_window,
                    velocity_threshold,
                    min_contact_frames,
                    visibility_threshold,
                )

                if verbose:
                    print_verbose_parameters(
                        video, characteristics, quality_preset, params
                    )

            smoothed_landmarks = apply_smoothing(
                landmarks_sequence, params, verbose, timer
            )

            if verbose:
                print("Extracting foot positions...")
            with timer.measure("vertical_position_extraction"):
                vertical_positions, visibilities = extract_vertical_positions(
                    smoothed_landmarks
                )

            if verbose:
                print("Detecting ground contact...")
            with timer.measure("ground_contact_detection"):
                contact_states = detect_ground_contact(
                    vertical_positions,
                    velocity_threshold=params.velocity_threshold,
                    min_contact_frames=params.min_contact_frames,
                    visibility_threshold=params.visibility_threshold,
                    visibilities=visibilities,
                    window_length=params.smoothing_window,
                    polyorder=params.polyorder,
                )

            if verbose:
                print("Calculating metrics...")
            with timer.measure("metrics_calculation"):
                metrics = calculate_drop_jump_metrics(
                    contact_states,
                    vertical_positions,
                    video.fps,
                    drop_start_frame=drop_start_frame,
                    velocity_threshold=params.velocity_threshold,
                    smoothing_window=params.smoothing_window,
                    polyorder=params.polyorder,
                    use_curvature=params.use_curvature,
                )

            if verbose:
                print("Assessing tracking quality...")
            with timer.measure("quality_assessment"):
                _, outlier_mask = reject_outliers(
                    vertical_positions,
                    use_ransac=True,
                    use_median=True,
                    interpolate=False,
                )

                phases = find_contact_phases(contact_states)
                phases_detected = len(phases) > 0
                phase_count = len(phases)

                quality_result = assess_jump_quality(
                    visibilities=visibilities,
                    positions=vertical_positions,
                    outlier_mask=outlier_mask,
                    fps=video.fps,
                    phases_detected=phases_detected,
                    phase_count=phase_count,
                )

            drop_frame = None
            if drop_start_frame is None and metrics.drop_start_frame is not None:
                drop_frame = metrics.drop_start_frame
            elif drop_start_frame is not None:
                drop_frame = drop_start_frame

            algorithm_config = AlgorithmConfig(
                detection_method="forward_search",
                tracking_method="mediapipe_pose",
                model_complexity=1,
                smoothing=SmoothingConfig(
                    window_size=params.smoothing_window,
                    polynomial_order=params.polyorder,
                    use_bilateral_filter=params.bilateral_filter,
                    use_outlier_rejection=params.outlier_rejection,
                ),
                detection=DetectionConfig(
                    velocity_threshold=params.velocity_threshold,
                    min_contact_frames=params.min_contact_frames,
                    visibility_threshold=params.visibility_threshold,
                    use_curvature_refinement=params.use_curvature,
                ),
                drop_detection=DropDetectionConfig(
                    auto_detect_drop_start=(drop_start_frame is None),
                    detected_drop_frame=drop_frame,
                    min_stationary_duration_s=0.5,
                ),
            )

            video_info = VideoInfo(
                source_path=video_path,
                fps=video.fps,
                width=video.width,
                height=video.height,
                duration_s=video.frame_count / video.fps,
                frame_count=video.frame_count,
                codec=video.codec,
            )

            if verbose and quality_result.warnings:
                print("\n⚠️  Quality Warnings:")
                for warning in quality_result.warnings:
                    print(f"  - {warning}")
                print()

            if output_video:
                if verbose:
                    print(f"Generating debug video: {output_video}")

                debug_h, debug_w = frames[0].shape[:2]
                if video.fps > 30:
                    debug_fps = video.fps / (video.fps / 30.0)
                else:
                    debug_fps = video.fps
                if len(frames) < len(landmarks_sequence):
                    step = max(1, int(video.fps / 30.0))
                    debug_fps = video.fps / step

                if timer:
                    with timer.measure("debug_video_generation"):
                        with DebugOverlayRenderer(
                            output_video,
                            debug_w,
                            debug_h,
                            debug_w,
                            debug_h,
                            debug_fps,
                            timer=timer,
                        ) as renderer:
                            for frame, idx in zip(frames, frame_indices, strict=True):
                                annotated = renderer.render_frame(
                                    frame,
                                    smoothed_landmarks[idx],
                                    contact_states[idx],
                                    idx,
                                    metrics,
                                    use_com=False,
                                )
                                renderer.write_frame(annotated)
                    with timer.measure("debug_video_reencode"):
                        pass
                else:
                    with DebugOverlayRenderer(
                        output_video,
                        debug_w,
                        debug_h,
                        debug_w,
                        debug_h,
                        debug_fps,
                        timer=timer,
                    ) as renderer:
                        for frame, idx in zip(frames, frame_indices, strict=True):
                            annotated = renderer.render_frame(
                                frame,
                                smoothed_landmarks[idx],
                                contact_states[idx],
                                idx,
                                metrics,
                                use_com=False,
                            )
                            renderer.write_frame(annotated)

                if verbose:
                    print(f"Debug video saved: {output_video}")

            with timer.measure("metrics_validation"):
                validator = DropJumpMetricsValidator()
                validation_result = validator.validate(metrics.to_dict())  # type: ignore[arg-type]
                metrics.validation_result = validation_result

            if verbose and validation_result.issues:
                print("\n⚠️  Validation Results:")
                for issue in validation_result.issues:
                    print(f"  [{issue.severity.value}] {issue.metric}: {issue.message}")

            processing_time = time.time() - start_time
            stage_times = convert_timer_to_stage_names(timer.get_metrics())

            processing_info = ProcessingInfo(
                version=get_kinemotion_version(),
                timestamp=create_timestamp(),
                quality_preset=quality_preset.value,
                processing_time_s=processing_time,
                timing_breakdown=stage_times,
            )

            result_metadata = ResultMetadata(
                quality=quality_result,
                video=video_info,
                processing=processing_info,
                algorithm=algorithm_config,
            )

            metrics.result_metadata = result_metadata

            if json_output:
                if timer:
                    with timer.measure("json_serialization"):
                        output_path = Path(json_output)
                        metrics_dict = metrics.to_dict()
                        json_str = json.dumps(metrics_dict, indent=2)
                        output_path.write_text(json_str)
                else:
                    output_path = Path(json_output)
                    metrics_dict = metrics.to_dict()
                    json_str = json.dumps(metrics_dict, indent=2)
                    output_path.write_text(json_str)

                if verbose:
                    print(f"Metrics written to: {json_output}")

            if verbose:
                total_time = time.time() - start_time
                stage_times_verbose = convert_timer_to_stage_names(timer.get_metrics())

                print("\n=== Timing Summary ===")
                for stage, duration in stage_times_verbose.items():
                    percentage = (duration / total_time) * 100
                    dur_ms = duration * 1000
                    print(f"{stage:.<40} {dur_ms:>6.0f}ms ({percentage:>5.1f}%)")
                total_ms = total_time * 1000
                print(f"{('Total'):.>40} {total_ms:>6.0f}ms (100.0%)")
                print()
                print("Analysis complete!")

            return metrics


def process_dropjump_videos_bulk(
    configs: list[DropJumpVideoConfig],
    max_workers: int = 4,
    progress_callback: Callable[[DropJumpVideoResult], None] | None = None,
) -> list[DropJumpVideoResult]:
    """
    Process multiple drop jump videos in parallel.
    """

    def error_factory(video_path: str, error_msg: str) -> DropJumpVideoResult:
        return DropJumpVideoResult(
            video_path=video_path, success=False, error=error_msg
        )

    return process_videos_bulk_generic(
        configs,
        _process_dropjump_video_wrapper,
        error_factory,
        max_workers,
        progress_callback,
    )


def _process_dropjump_video_wrapper(config: DropJumpVideoConfig) -> DropJumpVideoResult:
    """Wrapper function for parallel processing."""
    start_time = time.time()

    try:
        metrics = process_dropjump_video(
            video_path=config.video_path,
            quality=config.quality,
            output_video=config.output_video,
            json_output=config.json_output,
            drop_start_frame=config.drop_start_frame,
            smoothing_window=config.smoothing_window,
            velocity_threshold=config.velocity_threshold,
            min_contact_frames=config.min_contact_frames,
            visibility_threshold=config.visibility_threshold,
            detection_confidence=config.detection_confidence,
            tracking_confidence=config.tracking_confidence,
            verbose=False,
        )

        processing_time = time.time() - start_time

        return DropJumpVideoResult(
            video_path=config.video_path,
            success=True,
            metrics=metrics,
            processing_time=processing_time,
        )

    except Exception as e:
        processing_time = time.time() - start_time

        return DropJumpVideoResult(
            video_path=config.video_path,
            success=False,
            error=str(e),
            processing_time=processing_time,
        )


# ========== CMJ Analysis API ==========


@dataclass
class CMJVideoConfig:
    """Configuration for processing a single CMJ video."""

    video_path: str
    quality: str = "balanced"
    output_video: str | None = None
    json_output: str | None = None
    smoothing_window: int | None = None
    velocity_threshold: float | None = None
    min_contact_frames: int | None = None
    visibility_threshold: float | None = None
    detection_confidence: float | None = None
    tracking_confidence: float | None = None


@dataclass
class CMJVideoResult:
    """Result of processing a single CMJ video."""

    video_path: str
    success: bool
    metrics: CMJMetrics | None = None
    error: str | None = None
    processing_time: float = 0.0


def process_cmj_video(
    video_path: str,
    quality: str = "balanced",
    output_video: str | None = None,
    json_output: str | None = None,
    smoothing_window: int | None = None,
    velocity_threshold: float | None = None,
    min_contact_frames: int | None = None,
    visibility_threshold: float | None = None,
    detection_confidence: float | None = None,
    tracking_confidence: float | None = None,
    verbose: bool = False,
    timer: PerformanceTimer | None = None,
    pose_tracker: "PoseTracker | None" = None,
) -> CMJMetrics:
    """
    Process a single CMJ video and return metrics.

    CMJ (Counter Movement Jump) is performed at floor level without a drop box.
    Athletes start standing, perform a countermovement (eccentric phase), then
    jump upward (concentric phase).

    Args:
        video_path: Path to the input video file
        quality: Analysis quality preset ("fast", "balanced", or "accurate")
        output_video: Optional path for debug video output
        json_output: Optional path for JSON metrics output
        smoothing_window: Optional override for smoothing window
        velocity_threshold: Optional override for velocity threshold
        min_contact_frames: Optional override for minimum contact frames
        visibility_threshold: Optional override for visibility threshold
        detection_confidence: Optional override for pose detection confidence
        tracking_confidence: Optional override for pose tracking confidence
        verbose: Print processing details
        timer: Optional PerformanceTimer for measuring operations
        pose_tracker: Optional pre-initialized PoseTracker instance (reused if provided)

    Returns:
        CMJMetrics object containing analysis results

    Raises:
        ValueError: If video cannot be processed or parameters are invalid
        FileNotFoundError: If video file does not exist
    """
    if not Path(video_path).exists():
        raise FileNotFoundError(f"Video file not found: {video_path}")

    start_time = time.time()
    if timer is None:
        timer = PerformanceTimer()

    quality_preset = parse_quality_preset(quality)

    with timer.measure("video_initialization"):
        with VideoProcessor(video_path, timer=timer) as video:
            if verbose:
                print(
                    f"Video: {video.width}x{video.height} @ {video.fps:.2f} fps, "
                    f"{video.frame_count} frames"
                )

            det_conf, track_conf = determine_confidence_levels(
                quality_preset, detection_confidence, tracking_confidence
            )

            if verbose:
                print("Processing all frames with MediaPipe pose tracking...")

            tracker = pose_tracker
            should_close_tracker = False

            if tracker is None:
                tracker = PoseTracker(
                    min_detection_confidence=det_conf,
                    min_tracking_confidence=track_conf,
                    timer=timer,
                )
                should_close_tracker = True

            frames, landmarks_sequence, frame_indices = process_all_frames(
                video, tracker, verbose, timer, close_tracker=should_close_tracker
            )

            with timer.measure("parameter_auto_tuning"):
                characteristics = analyze_video_sample(
                    landmarks_sequence, video.fps, video.frame_count
                )
                params = auto_tune_parameters(characteristics, quality_preset)

                params = apply_expert_overrides(
                    params,
                    smoothing_window,
                    velocity_threshold,
                    min_contact_frames,
                    visibility_threshold,
                )

                if verbose:
                    print_verbose_parameters(
                        video, characteristics, quality_preset, params
                    )

            smoothed_landmarks = apply_smoothing(
                landmarks_sequence, params, verbose, timer
            )

            if verbose:
                print("Extracting vertical positions (Hip and Foot)...")
            with timer.measure("vertical_position_extraction"):
                vertical_positions, visibilities = extract_vertical_positions(
                    smoothed_landmarks, target="hip"
                )

                foot_positions, _ = extract_vertical_positions(
                    smoothed_landmarks, target="foot"
                )

            tracking_method = "hip_hybrid"

            if verbose:
                print("Detecting CMJ phases...")
            with timer.measure("phase_detection"):
                phases = detect_cmj_phases(
                    vertical_positions,
                    video.fps,
                    window_length=params.smoothing_window,
                    polyorder=params.polyorder,
                    landing_positions=foot_positions,
                )

            if phases is None:
                raise ValueError("Could not detect CMJ phases in video")

            standing_end, lowest_point, takeoff_frame, landing_frame = phases

            if verbose:
                print("Calculating metrics...")
            with timer.measure("metrics_calculation"):
                from .cmj.analysis import compute_signed_velocity

                velocities = compute_signed_velocity(
                    vertical_positions,
                    window_length=params.smoothing_window,
                    polyorder=params.polyorder,
                )

                metrics = calculate_cmj_metrics(
                    vertical_positions,
                    velocities,
                    standing_end,
                    lowest_point,
                    takeoff_frame,
                    landing_frame,
                    video.fps,
                    tracking_method=tracking_method,
                )

            if verbose:
                print("Assessing tracking quality...")
            with timer.measure("quality_assessment"):
                _, outlier_mask = reject_outliers(
                    vertical_positions,
                    use_ransac=True,
                    use_median=True,
                    interpolate=False,
                )

                phases_detected = True
                phase_count = 4

                quality_result = assess_jump_quality(
                    visibilities=visibilities,
                    positions=vertical_positions,
                    outlier_mask=outlier_mask,
                    fps=video.fps,
                    phases_detected=phases_detected,
                    phase_count=phase_count,
                )

            algorithm_config = AlgorithmConfig(
                detection_method="backward_search",
                tracking_method="mediapipe_pose",
                model_complexity=1,
                smoothing=SmoothingConfig(
                    window_size=params.smoothing_window,
                    polynomial_order=params.polyorder,
                    use_bilateral_filter=params.bilateral_filter,
                    use_outlier_rejection=params.outlier_rejection,
                ),
                detection=DetectionConfig(
                    velocity_threshold=params.velocity_threshold,
                    min_contact_frames=params.min_contact_frames,
                    visibility_threshold=params.visibility_threshold,
                    use_curvature_refinement=params.use_curvature,
                ),
                drop_detection=None,
            )

            video_info = VideoInfo(
                source_path=video_path,
                fps=video.fps,
                width=video.width,
                height=video.height,
                duration_s=video.frame_count / video.fps,
                frame_count=video.frame_count,
                codec=video.codec,
            )

            if verbose and quality_result.warnings:
                print("\n⚠️  Quality Warnings:")
                for warning in quality_result.warnings:
                    print(f"  - {warning}")
                print()

            if output_video:
                if verbose:
                    print(f"Generating debug video: {output_video}")

                debug_h, debug_w = frames[0].shape[:2]
                step = max(1, int(video.fps / 30.0))
                debug_fps = video.fps / step

                if timer:
                    with timer.measure("debug_video_generation"):
                        with CMJDebugOverlayRenderer(
                            output_video,
                            debug_w,
                            debug_h,
                            debug_w,
                            debug_h,
                            debug_fps,
                            timer=timer,
                        ) as renderer:
                            for frame, idx in zip(frames, frame_indices, strict=True):
                                annotated = renderer.render_frame(
                                    frame, smoothed_landmarks[idx], idx, metrics
                                )
                                renderer.write_frame(annotated)
                    with timer.measure("debug_video_reencode"):
                        pass
                else:
                    with CMJDebugOverlayRenderer(
                        output_video,
                        debug_w,
                        debug_h,
                        debug_w,
                        debug_h,
                        debug_fps,
                        timer=timer,
                    ) as renderer:
                        for frame, idx in zip(frames, frame_indices, strict=True):
                            annotated = renderer.render_frame(
                                frame, smoothed_landmarks[idx], idx, metrics
                            )
                            renderer.write_frame(annotated)

                if verbose:
                    print(f"Debug video saved: {output_video}")

            with timer.measure("metrics_validation"):
                validator = CMJMetricsValidator()
                validation_result = validator.validate(metrics.to_dict())  # type: ignore[arg-type]
                metrics.validation_result = validation_result

            processing_time = time.time() - start_time
            stage_times = convert_timer_to_stage_names(timer.get_metrics())

            processing_info = ProcessingInfo(
                version=get_kinemotion_version(),
                timestamp=create_timestamp(),
                quality_preset=quality_preset.value,
                processing_time_s=processing_time,
                timing_breakdown=stage_times,
            )

            result_metadata = ResultMetadata(
                quality=quality_result,
                video=video_info,
                processing=processing_info,
                algorithm=algorithm_config,
            )

            metrics.result_metadata = result_metadata

            if json_output:
                if timer:
                    with timer.measure("json_serialization"):
                        output_path = Path(json_output)
                        metrics_dict = metrics.to_dict()
                        json_str = json.dumps(metrics_dict, indent=2)
                        output_path.write_text(json_str)
                else:
                    output_path = Path(json_output)
                    metrics_dict = metrics.to_dict()
                    json_str = json.dumps(metrics_dict, indent=2)
                    output_path.write_text(json_str)

                if verbose:
                    print(f"Metrics written to: {json_output}")

            if verbose and validation_result.issues:
                print("\n⚠️  Validation Results:")
                for issue in validation_result.issues:
                    print(f"  [{issue.severity.value}] {issue.metric}: {issue.message}")

            if verbose:
                total_time = time.time() - start_time
                stage_times = convert_timer_to_stage_names(timer.get_metrics())

                print("\n=== Timing Summary ===")
                for stage, duration in stage_times.items():
                    percentage = (duration / total_time) * 100
                    dur_ms = duration * 1000
                    print(f"{stage:. <40} {dur_ms:>6.0f}ms ({percentage:>5.1f}%)")
                total_ms = total_time * 1000
                print(f"{('Total'):.>40} {total_ms:>6.0f}ms (100.0%)")
                print()

                print(f"\nJump height: {metrics.jump_height:.3f}m")
                print(f"Flight time: {metrics.flight_time * 1000:.1f}ms")
                print(f"Countermovement depth: {metrics.countermovement_depth:.3f}m")

            return metrics


def process_cmj_videos_bulk(
    configs: list[CMJVideoConfig],
    max_workers: int = 4,
    progress_callback: Callable[[CMJVideoResult], None] | None = None,
) -> list[CMJVideoResult]:
    """
    Process multiple CMJ videos in parallel.
    """

    def error_factory(video_path: str, error_msg: str) -> CMJVideoResult:
        return CMJVideoResult(video_path=video_path, success=False, error=error_msg)

    return process_videos_bulk_generic(
        configs,
        _process_cmj_video_wrapper,
        error_factory,
        max_workers,
        progress_callback,
    )


def _process_cmj_video_wrapper(config: CMJVideoConfig) -> CMJVideoResult:
    """Wrapper function for parallel CMJ processing."""
    start_time = time.time()

    try:
        metrics = process_cmj_video(
            video_path=config.video_path,
            quality=config.quality,
            output_video=config.output_video,
            json_output=config.json_output,
            smoothing_window=config.smoothing_window,
            velocity_threshold=config.velocity_threshold,
            min_contact_frames=config.min_contact_frames,
            visibility_threshold=config.visibility_threshold,
            detection_confidence=config.detection_confidence,
            tracking_confidence=config.tracking_confidence,
            verbose=False,
        )

        processing_time = time.time() - start_time

        return CMJVideoResult(
            video_path=config.video_path,
            success=True,
            metrics=metrics,
            processing_time=processing_time,
        )

    except Exception as e:
        processing_time = time.time() - start_time

        return CMJVideoResult(
            video_path=config.video_path,
            success=False,
            error=str(e),
            processing_time=processing_time,
        )
