import json
from pathlib import Path

from fastapi.testclient import TestClient

from app.main import create_app


def make_client(tmp_path: Path) -> TestClient:
    app = create_app(state_dir=tmp_path)
    return TestClient(app)


def test_health_endpoint_reports_ok(tmp_path: Path) -> None:
    client = make_client(tmp_path)

    response = client.get('/api/health')

    assert response.status_code == 200
    assert response.json() == {'status': 'ok'}


def test_dashboard_summary_seeds_john_and_manndra(tmp_path: Path) -> None:
    client = make_client(tmp_path)

    response = client.get('/api/dashboard/summary')

    assert response.status_code == 200
    payload = response.json()
    user_ids = [user['id'] for user in payload['users']]

    assert user_ids == ['john', 'manndra']
    assert payload['counts'] == {
        'exercise_templates': 31,
        'measurement_entries': 0,
        'routines': 5,
        'weight_entries': 0,
        'workout_sessions': 0,
    }
    john = next(user for user in payload['users'] if user['id'] == 'john')
    assert john['measurement_system'] == 'imperial'
    routine_names = [routine['name'] for routine in payload['routines']]
    assert routine_names == [
        'Monday: Chest',
        'Tuesday: Back',
        'Wednesday: Arms',
        'Thursday: Shoulders',
        'Friday: Legs',
    ]
    assert payload['routines'][0]['exercises'][0] == {
        'exercise_name': 'Bench Press',
        'target_sets': 3,
        'target_reps': '10',
    }
    assert '3 sets of 10 reps' in payload['routines'][0]['notes']
    assert 'Weights are still TBD' in payload['routines'][0]['notes']
    template_names = [template['name'] for template in payload['exercise_templates']]
    assert template_names == [
        'Resistance Band Crunches',
        'Calf Raises',
        'Deadlifts',
        'Step Ups',
        'Leg Extensions',
    ]


def test_updating_profile_and_logging_weight_computes_bmi(tmp_path: Path) -> None:
    client = make_client(tmp_path)

    profile_response = client.put(
        '/api/profile/john',
        json={
            'display_name': 'John',
            'height_inches': 68.5,
        },
    )
    assert profile_response.status_code == 200

    weight_response = client.post(
        '/api/profile/john/weight',
        json={
            'recorded_at': '2026-05-21T08:30:00',
            'weight_lbs': 220,
            'body_fat_percent': 24.5,
        },
    )

    assert weight_response.status_code == 201
    saved_entry = weight_response.json()
    assert saved_entry['user_id'] == 'john'
    assert saved_entry['weight_lbs'] == 220
    assert saved_entry['body_fat_percent'] == 24.5
    assert round(saved_entry['bmi'], 1) == 33.0

    summary = client.get('/api/dashboard/summary').json()
    john = next(user for user in summary['users'] if user['id'] == 'john')

    assert john['current_weight_lbs'] == 220
    assert round(john['current_bmi'], 1) == 33.0


def test_logging_measurements_tracks_latest_imperial_body_metrics(tmp_path: Path) -> None:
    client = make_client(tmp_path)

    response = client.post(
        '/api/profile/john/measurements',
        json={
            'recorded_at': '2026-05-21T08:45:00',
            'waist_inches': 42,
            'hip_inches': 41,
            'chest_inches': 46,
            'neck_inches': 17,
            'notes': 'Post-op baseline',
        },
    )

    assert response.status_code == 201
    saved_entry = response.json()
    assert saved_entry['user_id'] == 'john'
    assert saved_entry['waist_inches'] == 42
    assert saved_entry['hip_inches'] == 41
    assert saved_entry['chest_inches'] == 46
    assert saved_entry['neck_inches'] == 17

    summary = client.get('/api/dashboard/summary').json()
    john = next(user for user in summary['users'] if user['id'] == 'john')

    assert summary['counts']['measurement_entries'] == 1
    assert john['latest_measurements'] == {
        'recorded_at': '2026-05-21T08:45:00',
        'waist_inches': 42,
        'hip_inches': 41,
        'chest_inches': 46,
        'neck_inches': 17,
    }


def test_can_create_and_list_exercise_templates(tmp_path: Path) -> None:
    client = make_client(tmp_path)

    create_response = client.post(
        '/api/exercises/templates',
        json={
            'name': 'Bench Press',
            'primary_muscle': 'chest',
            'equipment': 'barbell',
            'notes': 'Flat bench barbell press.',
        },
    )

    assert create_response.status_code == 201
    saved_template = create_response.json()
    assert saved_template['name'] == 'Bench Press'
    assert saved_template['equipment'] == 'barbell'

    list_response = client.get('/api/exercises/templates')
    assert list_response.status_code == 200
    payload = list_response.json()
    assert len(payload) == 32
    assert any(
        item['name'] == 'Bench Press' and item['notes'] == 'Flat bench barbell press.'
        for item in payload
    )

    summary = client.get('/api/dashboard/summary').json()
    assert summary['counts']['exercise_templates'] == 32


def test_can_create_routine_before_real_program_arrives(tmp_path: Path) -> None:
    client = make_client(tmp_path)

    client.post(
        '/api/exercises/templates',
        json={
            'name': 'Bench Press',
            'primary_muscle': 'chest',
            'equipment': 'barbell',
        },
    )
    client.post(
        '/api/exercises/templates',
        json={
            'name': 'Incline Dumbbell Press',
            'primary_muscle': 'chest',
            'equipment': 'dumbbells',
        },
    )

    create_response = client.post(
        '/api/routines',
        json={
            'user_id': 'john',
            'name': 'Push Day Placeholder',
            'notes': 'Temporary until John sends the real split.',
            'exercises': [
                {'exercise_name': 'Bench Press', 'target_sets': 3, 'target_reps': '8-10'},
                {'exercise_name': 'Incline Dumbbell Press', 'target_sets': 3, 'target_reps': '10-12'},
            ],
        },
    )

    assert create_response.status_code == 201
    saved_routine = create_response.json()
    assert saved_routine['user_id'] == 'john'
    assert saved_routine['name'] == 'Push Day Placeholder'
    assert len(saved_routine['exercises']) == 2

    list_response = client.get('/api/routines')
    assert list_response.status_code == 200
    payload = list_response.json()
    assert len(payload) == 6
    assert any(routine['name'] == 'Push Day Placeholder' for routine in payload)

    summary = client.get('/api/dashboard/summary').json()
    assert summary['counts']['routines'] == 6


def test_history_endpoints_return_newest_first(tmp_path: Path) -> None:
    client = make_client(tmp_path)

    client.post(
        '/api/profile/john/weight',
        json={'recorded_at': '2026-05-20T08:30:00', 'weight_lbs': 222},
    )
    client.post(
        '/api/profile/john/weight',
        json={'recorded_at': '2026-05-21T08:30:00', 'weight_lbs': 220},
    )
    client.post(
        '/api/profile/john/measurements',
        json={'recorded_at': '2026-05-20T08:45:00', 'waist_inches': 43},
    )
    client.post(
        '/api/profile/john/measurements',
        json={'recorded_at': '2026-05-21T08:45:00', 'waist_inches': 42},
    )
    client.post(
        '/api/workouts',
        json={
            'user_id': 'john',
            'performed_at': '2026-05-20T09:15:00',
            'routine_name': 'Old Workout',
            'exercises': [{'exercise_name': 'Bench Press', 'sets': [{'reps': 8, 'weight_lbs': 135}]}],
        },
    )
    client.post(
        '/api/workouts',
        json={
            'user_id': 'john',
            'performed_at': '2026-05-21T09:15:00',
            'routine_name': 'New Workout',
            'exercises': [{'exercise_name': 'Bench Press', 'sets': [{'reps': 8, 'weight_lbs': 145}]}],
        },
    )

    weight_history = client.get('/api/history/john/weights')
    measurement_history = client.get('/api/history/john/measurements')
    workout_history = client.get('/api/history/john/workouts')

    assert weight_history.status_code == 200
    assert measurement_history.status_code == 200
    assert workout_history.status_code == 200

    assert [entry['weight_lbs'] for entry in weight_history.json()] == [220, 222]
    assert [entry['waist_inches'] for entry in measurement_history.json()] == [42, 43]
    assert [entry['routine_name'] for entry in workout_history.json()] == ['New Workout', 'Old Workout']


def test_logging_structured_workout_updates_recent_activity(tmp_path: Path) -> None:
    client = make_client(tmp_path)

    response = client.post(
        '/api/workouts',
        json={
            'user_id': 'john',
            'performed_at': '2026-05-21T09:15:00',
            'routine_name': 'Push Day',
            'notes': 'First pass',
            'exercises': [
                {
                    'exercise_name': 'Bench Press',
                    'sets': [
                        {'reps': 8, 'weight_lbs': 135, 'rpe': 7.5, 'rest_seconds': 120},
                        {'reps': 8, 'weight_lbs': 145, 'rpe': 8.0, 'rest_seconds': 120},
                    ],
                }
            ],
        },
    )

    assert response.status_code == 201
    workout = response.json()
    assert workout['user_id'] == 'john'
    assert workout['routine_name'] == 'Push Day'
    assert workout['exercise_count'] == 1
    assert workout['set_count'] == 2
    assert workout['total_volume_lbs'] == 2240

    summary = client.get('/api/dashboard/summary').json()
    assert summary['counts']['workout_sessions'] == 1
    assert summary['recent_workouts'][0]['routine_name'] == 'Push Day'
    assert summary['recent_workouts'][0]['total_volume_lbs'] == 2240


def test_progression_endpoint_summarizes_exercise_bests(tmp_path: Path) -> None:
    client = make_client(tmp_path)

    client.post(
        '/api/workouts',
        json={
            'user_id': 'john',
            'performed_at': '2026-05-20T09:15:00',
            'routine_name': 'Push Day',
            'exercises': [
                {
                    'exercise_name': 'Bench Press',
                    'sets': [
                        {'reps': 8, 'weight_lbs': 135},
                        {'reps': 5, 'weight_lbs': 155},
                    ],
                }
            ],
        },
    )
    client.post(
        '/api/workouts',
        json={
            'user_id': 'john',
            'performed_at': '2026-05-22T09:15:00',
            'routine_name': 'Push Day',
            'exercises': [
                {
                    'exercise_name': 'Bench Press',
                    'sets': [
                        {'reps': 3, 'weight_lbs': 175},
                    ],
                },
                {
                    'exercise_name': 'Incline Dumbbell Press',
                    'sets': [
                        {'reps': 10, 'weight_lbs': 55},
                    ],
                },
            ],
        },
    )

    response = client.get('/api/progression/john/exercises')

    assert response.status_code == 200
    payload = response.json()
    assert payload[0]['exercise_name'] == 'Bench Press'
    assert payload[0]['sessions'] == 2
    assert payload[0]['max_weight_lbs'] == 175
    assert payload[0]['best_set_estimated_1rm'] == 192.5
    assert payload[0]['last_performed_at'] == '2026-05-22T09:15:00'
    assert payload[1]['exercise_name'] == 'Incline Dumbbell Press'
    assert payload[1]['sessions'] == 1


def test_homepage_renders_core_sections_and_forms(tmp_path: Path) -> None:
    client = make_client(tmp_path)

    response = client.get('/')

    assert response.status_code == 200
    body = response.text
    assert 'Doris Barbell' in body
    assert 'Recent workouts' in body
    assert 'Current body metrics' in body
    assert 'Log weight' in body
    assert 'Add exercise template' in body
    assert 'Current routine board' in body
    assert 'Monday: Chest' in body


def test_existing_state_gets_seeded_routine_backfill(tmp_path: Path) -> None:
    state_path = tmp_path / 'state.json'
    state_path.write_text(
        json.dumps(
            {
                'users': {
                    'john': {
                        'id': 'john',
                        'display_name': 'John',
                        'is_stub': False,
                        'measurement_system': 'imperial',
                        'height_inches': 68.5,
                        'goal_weight_lbs': None,
                        'notes': 'Legacy state',
                    },
                    'manndra': {
                        'id': 'manndra',
                        'display_name': 'Manndra',
                        'is_stub': True,
                        'measurement_system': 'imperial',
                        'height_inches': None,
                        'goal_weight_lbs': None,
                        'notes': 'Legacy stub',
                    },
                },
                'exercise_templates': [],
                'routines': [],
                'weight_entries': [],
                'measurement_entries': [],
                'workout_sessions': [],
            }
        )
    )

    client = make_client(tmp_path)
    payload = client.get('/api/dashboard/summary').json()

    assert payload['counts']['exercise_templates'] == 31
    assert payload['counts']['routines'] == 5
    assert payload['routines'][0]['name'] == 'Monday: Chest'
    assert payload['routines'][0]['exercises'][0]['target_sets'] == 3
    assert payload['routines'][0]['exercises'][0]['target_reps'] == '10'


def test_existing_seeded_routines_upgrade_prescriptions_to_three_by_ten(tmp_path: Path) -> None:
    state_path = tmp_path / 'state.json'
    state_path.write_text(
        json.dumps(
            {
                'users': {
                    'john': {
                        'id': 'john',
                        'display_name': 'John',
                        'is_stub': False,
                        'measurement_system': 'imperial',
                        'height_inches': 68.5,
                        'goal_weight_lbs': None,
                        'notes': 'Legacy state',
                    },
                    'manndra': {
                        'id': 'manndra',
                        'display_name': 'Manndra',
                        'is_stub': True,
                        'measurement_system': 'imperial',
                        'height_inches': None,
                        'goal_weight_lbs': None,
                        'notes': 'Legacy stub',
                    },
                },
                'exercise_templates': [],
                'routines': [
                    {
                        'id': 'seed-routine-01',
                        'user_id': 'john',
                        'name': 'Monday: Chest',
                        'notes': 'Imported from John\'s current routine. Set and rep targets were not provided yet.',
                        'exercises': [
                            {'exercise_name': 'Bench Press', 'target_sets': 1, 'target_reps': 'TBD'},
                            {'exercise_name': 'Incline Bench Press', 'target_sets': 1, 'target_reps': 'TBD'},
                        ],
                    }
                ],
                'weight_entries': [],
                'measurement_entries': [],
                'workout_sessions': [],
            }
        )
    )

    client = make_client(tmp_path)
    payload = client.get('/api/dashboard/summary').json()

    monday = next(routine for routine in payload['routines'] if routine['name'] == 'Monday: Chest')
    assert monday['exercises'][0]['target_sets'] == 3
    assert monday['exercises'][0]['target_reps'] == '10'
    assert 'Weights are still TBD' in monday['notes']


def test_weight_form_submission_redirects_and_updates_dashboard(tmp_path: Path) -> None:
    client = make_client(tmp_path)

    response = client.post(
        '/log/weight',
        data={
            'user_id': 'john',
            'recorded_at': '2026-05-22T06:15:00',
            'weight_lbs': '218.5',
            'body_fat_percent': '23.7',
            'notes': 'Pre-gym weigh-in',
        },
        follow_redirects=False,
    )

    assert response.status_code == 303
    assert response.headers['location'] == '/'

    summary = client.get('/api/dashboard/summary').json()
    john = next(user for user in summary['users'] if user['id'] == 'john')
    assert john['current_weight_lbs'] == 218.5

    history = client.get('/api/history/john/weights').json()
    assert history[0]['notes'] == 'Pre-gym weigh-in'


def test_template_and_routine_form_submissions_redirect_and_render_entries(tmp_path: Path) -> None:
    client = make_client(tmp_path)

    template_response = client.post(
        '/templates',
        data={
            'name': 'Leg Press',
            'primary_muscle': 'quads',
            'equipment': 'machine',
            'notes': 'Plate-loaded sled.',
        },
        follow_redirects=False,
    )
    assert template_response.status_code == 303
    assert template_response.headers['location'] == '/'

    routine_response = client.post(
        '/routines',
        data={
            'user_id': 'john',
            'name': 'Leg Day Draft',
            'notes': 'Temporary until John sends the real split.',
            'exercise_lines': 'Leg Press|4|10-12\nHamstring Curl|3|10-12|Slow negative',
        },
        follow_redirects=False,
    )
    assert routine_response.status_code == 303
    assert routine_response.headers['location'] == '/'

    page = client.get('/')
    assert page.status_code == 200
    assert 'Leg Press' in page.text

    routines = client.get('/api/routines').json()
    saved_routine = next(routine for routine in routines if routine['name'] == 'Leg Day Draft')
    assert saved_routine['exercises'][0]['exercise_name'] == 'Leg Press'
    assert saved_routine['exercises'][1]['notes'] == 'Slow negative'
