import math

import pytest

from hef.catalog import Record
from hef.selection import Coordinate, Weights, distance, record_coordinate


def make_record(**overrides):
    base = dict(
        id="r",
        title="t",
        source_url="u",
        source_archive="internet_archive",
        license="public_domain",
        mode="video",
        left=0,
        right=0,
        dark=0,
        light=0,
        duration_s=600,
        file_path="/media/r.mp4",
    )
    base.update(overrides)
    return Record(**base)


def test_distance_zero_for_same_point():
    c = Coordinate(2, 2, 2, 2)
    assert distance(c, c) == 0.0


def test_distance_is_euclidean_by_default():
    a = Coordinate(0, 0, 0, 0)
    b = Coordinate(1, 1, 1, 1)
    assert distance(a, b) == pytest.approx(2.0)  # sqrt(1+1+1+1)


def test_weights_scale_planes_independently():
    a = Coordinate(0, 0, 0, 0)
    brain_only = Coordinate(1, 0, 0, 0)
    mood_only = Coordinate(0, 0, 1, 0)
    w = Weights(brain=4.0, mood=1.0)
    assert distance(a, brain_only, w) == pytest.approx(2.0)   # sqrt(4*1)
    assert distance(a, mood_only, w) == pytest.approx(1.0)    # sqrt(1*1)


def test_record_coordinate_extracts_axes():
    record = make_record(left=1, right=2, dark=3, light=4)
    assert record_coordinate(record) == Coordinate(1, 2, 3, 4)


from hef.selection import candidates_for_mode


def test_candidates_filter_to_exact_mode():
    records = [make_record(id="v", mode="video"), make_record(id="a", mode="audio")]
    result = candidates_for_mode(records, "audio", pool_size=4)
    assert [r.id for r in result] == ["a"]


def test_av_does_not_fall_back_when_enough_av():
    records = [make_record(id=f"av{i}", mode="av") for i in range(4)]
    records.append(make_record(id="a", mode="audio"))
    result = candidates_for_mode(records, "av", pool_size=4)
    assert {r.id for r in result} == {"av0", "av1", "av2", "av3"}


def test_av_falls_back_to_audio_and_video_when_thin():
    records = [
        make_record(id="av0", mode="av"),
        make_record(id="a", mode="audio"),
        make_record(id="v", mode="video"),
    ]
    result = candidates_for_mode(records, "av", pool_size=4)
    assert {r.id for r in result} == {"av0", "a", "v"}


def test_candidates_rejects_none_mode():
    with pytest.raises(ValueError):
        candidates_for_mode([], "none", pool_size=4)


import random

from hef.selection import select


def test_none_mode_returns_none():
    records = [make_record(id="v", mode="video")]
    assert select(records, Coordinate(0, 0, 0, 0), "none") is None


def test_empty_pool_returns_none():
    assert select([], Coordinate(0, 0, 0, 0), "video") is None


def test_select_returns_nearest_by_default():
    near = make_record(id="near", mode="video", left=2, right=2, dark=2, light=2)
    far = make_record(id="far", mode="video", left=0, right=0, dark=0, light=0)
    result = select([far, near], Coordinate(2, 2, 2, 2), "video")
    assert result.id == "near"


def test_select_is_deterministic_without_rng():
    records = [
        make_record(id="b", mode="video", left=1),
        make_record(id="a", mode="video", left=1),
    ]
    # Equal distance -> tie broken by id, so "a" wins deterministically.
    result = select(records, Coordinate(1, 0, 0, 0), "video")
    assert result.id == "a"


def test_select_with_rng_picks_within_nearest_pool():
    records = [make_record(id=f"r{i}", mode="video", left=i % 5) for i in range(10)]
    coord = Coordinate(2, 0, 0, 0)
    rng = random.Random(0)
    chosen = {select(records, coord, "video", pool_size=3, rng=rng).id for _ in range(50)}
    # Only records inside the 3-nearest pool may ever be chosen.
    ranked = sorted(records, key=lambda r: abs(r.left - 2))
    allowed = {r.id for r in ranked[:3]}
    assert chosen <= allowed


def test_approved_only_excludes_proposed():
    proposed = make_record(id="p", mode="video", review_status="proposed")
    approved = make_record(id="ok", mode="video", review_status="approved")
    result = select([proposed, approved], Coordinate(0, 0, 0, 0), "video",
                    approved_only=True)
    assert result.id == "ok"


def test_invalid_selector_mode_raises():
    with pytest.raises(ValueError):
        select([], Coordinate(0, 0, 0, 0), "telepathy")
