"""Tests for #1256: random asset selection and random start_time in preview generation."""

from unittest.mock import MagicMock

import pytest

from packages.domain.edit_plan_clip import EditPlanClip
from packages.domain.plan_generator_utils import (
    _calc_random_start_time,
    distribute_assets,
)


class TestAssignAssetWithStartTime:
    """Test assign_asset() with optional start_time parameter."""

    def test_assign_asset_without_start_time(self):
        """Backward compatible: assign_asset without start_time keeps default 0.0"""
        clip = EditPlanClip.create(plan_id="p1", clip_type="main", order=1, duration=5.0)
        clip.assign_asset("asset1")
        assert clip.asset_id == "asset1"
        assert clip.start_time == 0.0

    def test_assign_asset_with_valid_start_time(self):
        """assign_asset with valid start_time sets it correctly"""
        clip = EditPlanClip.create(plan_id="p1", clip_type="main", order=1, duration=5.0)
        clip.assign_asset("asset1", start_time=3.5)
        assert clip.asset_id == "asset1"
        assert clip.start_time == 3.5

    def test_assign_asset_with_zero_start_time(self):
        """assign_asset with start_time=0.0 sets it to 0.0"""
        clip = EditPlanClip.create(plan_id="p1", clip_type="main", order=1, duration=5.0, start_time=5.0)
        clip.assign_asset("asset1", start_time=0.0)
        assert clip.start_time == 0.0

    def test_assign_asset_with_none_start_time(self):
        """assign_asset with start_time=None keeps existing start_time"""
        clip = EditPlanClip.create(plan_id="p1", clip_type="main", order=1, duration=5.0, start_time=2.0)
        clip.assign_asset("asset1", start_time=None)
        assert clip.start_time == 2.0  # unchanged

    def test_assign_asset_with_negative_start_time(self):
        """assign_asset with negative start_time is ignored"""
        clip = EditPlanClip.create(plan_id="p1", clip_type="main", order=1, duration=5.0, start_time=2.0)
        clip.assign_asset("asset1", start_time=-1.0)
        assert clip.start_time == 2.0  # unchanged, negative ignored


class TestCalcRandomStartTime:
    """Test _calc_random_start_time helper function."""

    def test_returns_none_when_no_durations(self):
        """Returns None when asset_durations is None"""
        result = _calc_random_start_time("asset1", 5.0, None)
        assert result is None

    def test_returns_none_when_asset_not_in_durations(self):
        """Returns None when asset_id not in durations dict"""
        result = _calc_random_start_time("asset1", 5.0, {"other": 30.0})
        assert result is None

    def test_returns_zero_when_duration_too_short(self):
        """Returns 0.0 when asset duration <= clip duration"""
        result = _calc_random_start_time("asset1", 10.0, {"asset1": 5.0})
        assert result == 0.0

    def test_returns_valid_random_start_time(self):
        """Returns start_time within valid range"""
        import random

        random.seed(42)
        result = _calc_random_start_time("asset1", 5.0, {"asset1": 30.0})
        # max_start = 30.0 - 5.0 = 25.0
        assert 0.0 <= result <= 25.0

    def test_returns_zero_when_duration_zero(self):
        """Returns None when asset duration is 0"""
        result = _calc_random_start_time("asset1", 5.0, {"asset1": 0.0})
        assert result is None


class TestDistributeAssetsRandom:
    """Test distribute_assets() with random_selection parameter."""

    def test_one_take_without_random(self):
        """ONE_TAKE without random: assets assigned in order"""
        clips = [EditPlanClip.create(plan_id="p1", clip_type="main", order=i, duration=5.0) for i in range(3)]
        assets = ["a1", "a2", "a3", "a4"]
        distribute_assets(clips, assets, "one_take", random_selection=False)

        assert clips[0].asset_id == "a1"
        assert clips[1].asset_id == "a2"
        assert clips[2].asset_id == "a3"

    def test_one_take_with_random(self):
        """ONE_TAKE with random: assets assigned in random order"""
        clips = [EditPlanClip.create(plan_id="p1", clip_type="main", order=i, duration=5.0) for i in range(3)]
        assets = ["a1", "a2", "a3", "a4"]

        # Run multiple times to verify randomness
        results = set()
        for _ in range(10):
            distribute_assets(clips, assets, "one_take", random_selection=True)
            results.add(tuple(c.asset_id for c in clips))

        # Should have multiple different orderings
        assert len(results) > 1, "Random selection should produce different orderings"

    def test_with_asset_durations_sets_start_time(self):
        """distribute_assets with asset_durations sets random start_time"""
        clips = [EditPlanClip.create(plan_id="p1", clip_type="main", order=0, duration=5.0)]
        durations = {"a1": 30.0}
        distribute_assets(clips, ["a1"], "one_take", asset_durations=durations)

        assert clips[0].asset_id == "a1"
        # start_time should be set (0 <= start_time <= 25.0)
        assert 0.0 <= clips[0].start_time <= 25.0

    def test_pip_mode_with_random(self):
        """PIP mode with random selection works correctly"""
        clips = [
            EditPlanClip.create(plan_id="p1", clip_type="main", order=0, duration=5.0),
            EditPlanClip.create(plan_id="p1", clip_type="overlay", order=1, duration=5.0),
            EditPlanClip.create(plan_id="p1", clip_type="overlay", order=2, duration=5.0),
        ]
        assets = ["a1", "a2", "a3"]
        distribute_assets(clips, assets, "pip", random_selection=True)

        # All clips should have assets assigned
        assert clips[0].asset_id
        assert clips[1].asset_id
        assert clips[2].asset_id

    def test_voice_pip_mode_with_random(self):
        """VOICE_PIP mode with random selection works correctly"""
        clips = [
            EditPlanClip.create(plan_id="p1", clip_type="background", order=0, duration=5.0),
            EditPlanClip.create(plan_id="p1", clip_type="corner_voice", order=1, duration=5.0),
            EditPlanClip.create(plan_id="p1", clip_type="b_roll", order=2, duration=5.0),
        ]
        assets = ["a1", "a2", "a3"]
        durations = {"a1": 30.0, "a2": 25.0, "a3": 20.0}
        distribute_assets(clips, assets, "voice_pip", random_selection=True, asset_durations=durations)

        # All clips should have assets and start_times
        for clip in clips:
            assert clip.asset_id
            assert 0.0 <= clip.start_time <= 25.0  # max_start = duration - clip_duration

    def test_does_not_modify_original_list(self):
        """random_selection should not modify the original asset_ids list"""
        clips = [EditPlanClip.create(plan_id="p1", clip_type="main", order=0, duration=5.0)]
        assets = ["a1", "a2", "a3"]
        original = list(assets)
        distribute_assets(clips, assets, "one_take", random_selection=True)

        assert assets == original, "Original asset_ids list should not be modified"


class TestDistributeAssetsBackwardCompatible:
    """Ensure backward compatibility - existing calls without new params still work."""

    def test_distribute_assets_default_params(self):
        """distribute_assets works with just required params"""
        clips = [
            EditPlanClip.create(plan_id="p1", clip_type="main", order=0, duration=5.0),
            EditPlanClip.create(plan_id="p1", clip_type="main", order=1, duration=5.0),
        ]
        distribute_assets(clips, ["a1", "a2"], "one_take")

        assert clips[0].asset_id == "a1"
        assert clips[1].asset_id == "a2"
        assert clips[0].start_time == 0.0  # default

    def test_all_modes_work_without_new_params(self):
        """All editing modes work without random_selection/asset_durations"""
        # one_take / voice_over: use main clips
        for mode in ["one_take", "voice_over"]:
            clips = [
                EditPlanClip.create(plan_id="p1", clip_type="main", order=0, duration=5.0),
            ]
            distribute_assets(clips, ["a1"], mode)
            assert clips[0].asset_id == "a1"

        # pip: main + overlay
        clips = [
            EditPlanClip.create(plan_id="p1", clip_type="main", order=0, duration=5.0),
            EditPlanClip.create(plan_id="p1", clip_type="overlay", order=1, duration=5.0),
        ]
        distribute_assets(clips, ["a1"], "pip")
        assert clips[0].asset_id == "a1"  # first asset goes to main

        # voice_pip: background + corner_voice + b_roll
        clips = [
            EditPlanClip.create(plan_id="p1", clip_type="background", order=0, duration=5.0),
            EditPlanClip.create(plan_id="p1", clip_type="corner_voice", order=1, duration=5.0),
        ]
        distribute_assets(clips, ["a1"], "voice_pip")
        assert clips[0].asset_id == "a1"  # first asset goes to background
