import os

import pytest
from django.contrib.auth import get_user_model
from scrobbles.importers.webdav import scan_webdav_for_flexify
from scrobbles.models import Scrobble
from workouts.importer import (
    FLEXIFY_EXERCISE_ALIASES,
    aggregate_bodyweight,
    aggregate_workouts,
    cluster_sessions,
    import_flexify,
    read_flexify_sqlite,
    read_plans_csv,
    resolve_exercise,
)
from workouts.models import Exercise, WorkoutRoutine, WorkoutRoutineExercise

User = get_user_model()

SAMPLE_SQLITE = os.path.join(
    os.path.dirname(__file__), "..", "..", "data", "flexify-example.sqlite"
)
SAMPLE_CSV = os.path.join(
    os.path.dirname(__file__), "..", "..", "data", "flexify-plans-example.csv"
)


@pytest.fixture
def user(db):
    return User.objects.create(email="lifter@example.com")


def test_read_plans_csv():
    plans = read_plans_csv(SAMPLE_CSV)
    assert len(plans) == 3
    assert plans[0]["days"] == "Monday"
    assert plans[0]["title"] == "Middle Distance - Push Day"
    assert plans[0]["exercises"] == [
        "Barbell bench press",
        "Squat",
        "Lat pull-down",
        "Leg press",
    ]


def test_read_flexify_sqlite():
    data = read_flexify_sqlite(SAMPLE_SQLITE)
    assert len(data["plans"]) == 3
    assert len(data["plan_exercises"]) == 12
    assert len(data["catalog"]) == 58
    assert data["strength_unit"] == "lb"
    assert len(data["sets"]) == 12
    first = data["sets"][0]
    assert first["name"] == "Leg press"
    assert first["unit"] == "lb"
    assert first["plan_id"] == 3


def test_resolve_exercise_alias(db):
    exercise, created = resolve_exercise("Barbell bench press")
    assert exercise.name == "Barbell Bench Press - Medium Grip"
    assert created is True


def test_resolve_exercise_exact_name(db):
    exercise, created = resolve_exercise("Leg press")
    assert exercise.name == "Leg Press"
    assert created is True


def test_resolve_exercise_dry_run_does_not_create(db):
    before = Exercise.objects.count()
    exercise, created = resolve_exercise("Some Unknown Exercise", dry_run=True)
    assert exercise is None
    assert created is True
    assert Exercise.objects.count() == before


def test_resolve_exercise_reuses_existing(db):
    first, _ = resolve_exercise("Push-up")
    second, created = resolve_exercise("Push-up")
    assert second.id == first.id
    assert created is False


def test_cluster_sessions_splits_on_time_gap():
    sets = [
        {"name": "A", "created": 1000, "id": 1, "plan_id": 1},
        {"name": "B", "created": 1010, "id": 2, "plan_id": 1},
        {"name": "C", "created": 1000 + 61 * 60, "id": 3, "plan_id": 1},
        {"name": "D", "created": 1001 + 61 * 60, "id": 4, "plan_id": 1},
    ]
    sessions = cluster_sessions(sets, gap_minutes=60)
    assert len(sessions) == 2
    assert [s["name"] for s in sessions[0]] == ["A", "B"]
    assert [s["name"] for s in sessions[1]] == ["C", "D"]


def test_cluster_sessions_splits_on_plan_change():
    sets = [
        {"name": "A", "created": 1000, "id": 1, "plan_id": 1},
        {"name": "B", "created": 1005, "id": 2, "plan_id": 2},
    ]
    sessions = cluster_sessions(sets, gap_minutes=60)
    assert len(sessions) == 2
    assert [s["name"] for s in sessions[0]] == ["A"]
    assert [s["name"] for s in sessions[1]] == ["B"]


def test_aggregate_workouts_weights(db):
    session = read_flexify_sqlite(SAMPLE_SQLITE)["sets"][:3]
    workouts = aggregate_workouts(session)
    assert len(workouts) == 1
    entry = workouts[0]
    assert entry["sets"] == 3
    assert entry["reps"] == 10
    assert entry["weight_kg"] == pytest.approx(54.43, abs=0.01)


def test_aggregate_bodyweight():
    session = read_flexify_sqlite(SAMPLE_SQLITE)["sets"]
    assert aggregate_bodyweight(session) == pytest.approx(83.91, abs=0.01)


def test_import_flexify_dry_run(user):
    result = import_flexify(
        sqlite_path=SAMPLE_SQLITE,
        csv_path=SAMPLE_CSV,
        user_id=user.id,
        dry_run=True,
    )
    assert result["routines_created"] == 3
    assert result["routines_updated"] == 0
    assert result["scrobbles_created"] == 1
    assert Scrobble.objects.count() == 0
    assert WorkoutRoutine.objects.count() == 0


def test_import_flexify_full(user):
    result = import_flexify(
        sqlite_path=SAMPLE_SQLITE,
        csv_path=SAMPLE_CSV,
        user_id=user.id,
    )
    assert result["routines_created"] == 3
    assert result["scrobbles_created"] == 1
    assert result["scrobbles_skipped"] == 0

    scrobble = Scrobble.objects.get(user=user)
    assert scrobble.source == "Flexify Import"
    assert scrobble.played_to_completion is True
    assert scrobble.in_progress is False
    assert scrobble.workout_routine.title == "Middle Distance - Arm Day"

    entry = scrobble.logdata.workouts[0]
    assert entry["exercise_id"] is not None
    assert entry["sets"] == 3
    assert entry["reps"] == 10
    assert entry["weight_kg"] == pytest.approx(54.43, abs=0.01)
    assert scrobble.logdata.bodyweight_kg == pytest.approx(83.91, abs=0.01)
    assert scrobble.logdata.duration_minutes == 2

    planned = WorkoutRoutineExercise.objects.filter(
        routine=scrobble.workout_routine
    ).order_by("sequence")
    assert [pe.exercise.name for pe in planned] == [
        "Leg Press",
        "Pullups",
        "Pushups",
        "Crunches",
    ]
    assert result["exercises_created"] == 11


def test_import_flexify_idempotent(user):
    import_flexify(sqlite_path=SAMPLE_SQLITE, csv_path=SAMPLE_CSV, user_id=user.id)
    result = import_flexify(
        sqlite_path=SAMPLE_SQLITE,
        csv_path=SAMPLE_CSV,
        user_id=user.id,
    )
    assert result["routines_created"] == 0
    assert result["routines_updated"] == 3
    assert result["scrobbles_created"] == 0
    assert result["scrobbles_existing"] == 1
    assert Scrobble.objects.filter(user=user).count() == 1


def test_import_flexify_csv_only(user):
    result = import_flexify(
        csv_path=SAMPLE_CSV,
        user_id=user.id,
    )
    assert result["routines_created"] == 3
    assert result["scrobbles_created"] == 0


def test_import_flexify_sqlite_only(user):
    result = import_flexify(
        sqlite_path=SAMPLE_SQLITE,
        user_id=user.id,
    )
    assert result["scrobbles_created"] == 1
    routine_titles = set(WorkoutRoutine.objects.values_list("title", flat=True))
    assert "Friday" in routine_titles


def test_alias_targets_exist_in_wrkout_catalog():
    assert FLEXIFY_EXERCISE_ALIASES["Pull-up"] == "Pullups"
    assert FLEXIFY_EXERCISE_ALIASES["Push-up"] == "Pushups"
    assert FLEXIFY_EXERCISE_ALIASES["Crunch"] == "Crunches"
    assert FLEXIFY_EXERCISE_ALIASES["Deadlift"] == "Barbell Deadlift"
    assert FLEXIFY_EXERCISE_ALIASES["Squat"] == "Barbell Squat"


class FakeWebDAVClient:
    def __init__(self, files):
        self.files = {
            "var/workouts/": None,
            **{f"var/workouts/{name}": data for name, data in files.items()},
        }

    def info(self, path):
        if not any(k.startswith(path) or k == path for k in self.files):
            raise FileNotFoundError(path)

    def list(self, path, get_info=False):
        names = [k[len(path) :] for k in self.files if k.startswith(path) and k != path]
        if get_info:
            return [{"path": f"{path}{name}"} for name in names]
        return names

    def mkdir(self, path, recursive=False):
        self.files.setdefault(path, None)

    def download_sync(self, remote_path, local_path):
        with open(local_path, "wb") as f:
            f.write(self.files[remote_path])

    def move(self, src, dst):
        self.files[dst] = self.files.pop(src)


def test_scan_webdav_for_flexify(user, monkeypatch, tmp_path):
    with open(SAMPLE_SQLITE, "rb") as f:
        sqlite_bytes = f.read()
    with open(SAMPLE_CSV, "rb") as f:
        csv_bytes = f.read()

    client = FakeWebDAVClient(
        {
            "flexify-backup.sqlite": sqlite_bytes,
            "flexify-plans.csv": csv_bytes,
        }
    )

    import scrobbles.tasks as scrobbles_tasks
    from workouts.models import WorkoutImport

    queued = []
    monkeypatch.setattr(
        scrobbles_tasks,
        "process_workout_import",
        type("Task", (), {"delay": lambda self, pk: queued.append(pk)})(),
    )

    count = scan_webdav_for_flexify(client, user.id)
    assert count == 1
    assert len(queued) == 1
    assert "var/workouts/flexify-backup.sqlite" not in client.files
    assert "var/workouts/flexify-plans.csv" not in client.files
    assert any(
        k.startswith("var/workouts/processed/") and k.endswith(".sqlite")
        for k in client.files
    )

    imp = WorkoutImport.objects.get(pk=queued[0])
    assert imp.user_id == user.id
    assert imp.original_filename == "flexify-backup.sqlite"
    assert imp.file_hash
    assert imp.csv_file


def test_scan_webdav_for_flexify_skips_unchanged(user, monkeypatch, tmp_path):
    with open(SAMPLE_SQLITE, "rb") as f:
        sqlite_bytes = f.read()

    client = FakeWebDAVClient({"flexify-backup.sqlite": sqlite_bytes})

    import scrobbles.tasks as scrobbles_tasks
    from workouts.models import WorkoutImport

    queued = []
    monkeypatch.setattr(
        scrobbles_tasks,
        "process_workout_import",
        type("Task", (), {"delay": lambda self, pk: queued.append(pk)})(),
    )

    assert scan_webdav_for_flexify(client, user.id) == 1
    assert len(queued) == 1

    client.files["var/workouts/flexify-backup.sqlite"] = sqlite_bytes

    count = scan_webdav_for_flexify(client, user.id)
    assert count == 0
    assert len(queued) == 1
