from __future__ import annotations

from fastapi import APIRouter, Form, HTTPException, Request, status
from fastapi.responses import RedirectResponse
from fastapi.templating import Jinja2Templates

router = APIRouter()
templates = Jinja2Templates(directory='app/templates')


@router.get('/')
def home(request: Request):
    return templates.TemplateResponse(
        request=request,
        name='dashboard.html',
        context=_dashboard_context(request),
    )


@router.post('/log/weight', status_code=303)
def log_weight(
    request: Request,
    user_id: str = Form(...),
    recorded_at: str = Form(...),
    weight_lbs: float = Form(...),
    body_fat_percent: str = Form(''),
    notes: str = Form(''),
):
    payload = {
        'recorded_at': recorded_at,
        'weight_lbs': weight_lbs,
        'body_fat_percent': _optional_float(body_fat_percent),
        'notes': notes.strip() or None,
    }
    request.app.state.store.add_weight_entry(user_id, payload)
    return RedirectResponse(url='/', status_code=status.HTTP_303_SEE_OTHER)


@router.post('/log/measurements', status_code=303)
def log_measurements(
    request: Request,
    user_id: str = Form(...),
    recorded_at: str = Form(...),
    waist_inches: str = Form(''),
    hip_inches: str = Form(''),
    chest_inches: str = Form(''),
    neck_inches: str = Form(''),
    notes: str = Form(''),
):
    payload = {
        'recorded_at': recorded_at,
        'waist_inches': _optional_float(waist_inches),
        'hip_inches': _optional_float(hip_inches),
        'chest_inches': _optional_float(chest_inches),
        'neck_inches': _optional_float(neck_inches),
        'notes': notes.strip() or None,
    }
    if not any(
        payload[key] is not None
        for key in ('waist_inches', 'hip_inches', 'chest_inches', 'neck_inches')
    ):
        raise HTTPException(status_code=400, detail='At least one body measurement is required.')
    request.app.state.store.add_measurement_entry(user_id, payload)
    return RedirectResponse(url='/', status_code=status.HTTP_303_SEE_OTHER)


@router.post('/templates', status_code=303)
def create_template(
    request: Request,
    name: str = Form(...),
    primary_muscle: str = Form(''),
    equipment: str = Form(''),
    notes: str = Form(''),
):
    request.app.state.store.add_exercise_template(
        {
            'name': name.strip(),
            'primary_muscle': primary_muscle.strip() or None,
            'equipment': equipment.strip() or None,
            'notes': notes.strip() or None,
        }
    )
    return RedirectResponse(url='/', status_code=status.HTTP_303_SEE_OTHER)


@router.post('/routines', status_code=303)
def create_routine(
    request: Request,
    user_id: str = Form(...),
    name: str = Form(...),
    exercise_lines: str = Form(...),
    notes: str = Form(''),
):
    payload = {
        'user_id': user_id,
        'name': name.strip(),
        'notes': notes.strip() or None,
        'exercises': _parse_routine_exercises(exercise_lines),
    }
    request.app.state.store.add_routine(payload)
    return RedirectResponse(url='/', status_code=status.HTTP_303_SEE_OTHER)


@router.post('/workouts/quick-log', status_code=303)
def quick_log_workout(
    request: Request,
    user_id: str = Form(...),
    performed_at: str = Form(...),
    routine_name: str = Form(...),
    exercise_lines: str = Form(...),
    notes: str = Form(''),
):
    payload = {
        'user_id': user_id,
        'performed_at': performed_at,
        'routine_name': routine_name.strip(),
        'notes': notes.strip() or None,
        'exercises': _parse_workout_exercises(exercise_lines),
    }
    request.app.state.store.add_workout(payload)
    return RedirectResponse(url='/', status_code=status.HTTP_303_SEE_OTHER)


def _dashboard_context(request: Request) -> dict:
    store = request.app.state.store
    summary = store.dashboard_summary()
    return {
        'title': 'Doris Barbell',
        'summary': summary,
        'john_weight_history': store.weight_history('john')[:5],
        'john_measurement_history': store.measurement_history('john')[:5],
        'john_workout_history': store.workout_history('john')[:5],
        'john_progression': store.exercise_progression('john')[:5],
    }


def _optional_float(value: str) -> float | None:
    cleaned = value.strip()
    if not cleaned:
        return None
    try:
        return float(cleaned)
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=f'Invalid numeric value: {value}') from exc


def _parse_routine_exercises(raw_lines: str) -> list[dict]:
    exercises = []
    for line in raw_lines.splitlines():
        cleaned = line.strip()
        if not cleaned:
            continue
        parts = [part.strip() for part in cleaned.split('|')]
        if len(parts) < 3:
            raise HTTPException(
                status_code=400,
                detail='Routine lines must use exercise|sets|reps|optional notes format.',
            )
        exercise_name, target_sets_raw, target_reps = parts[:3]
        try:
            target_sets = int(target_sets_raw)
        except ValueError as exc:
            raise HTTPException(status_code=400, detail=f'Invalid target sets: {target_sets_raw}') from exc
        exercises.append(
            {
                'exercise_name': exercise_name,
                'target_sets': target_sets,
                'target_reps': target_reps,
                'notes': parts[3] if len(parts) > 3 and parts[3] else None,
            }
        )
    if not exercises:
        raise HTTPException(status_code=400, detail='At least one routine exercise is required.')
    return exercises


def _parse_workout_exercises(raw_lines: str) -> list[dict]:
    exercises = []
    for line in raw_lines.splitlines():
        cleaned = line.strip()
        if not cleaned:
            continue
        parts = [part.strip() for part in cleaned.split('|')]
        if len(parts) < 2:
            raise HTTPException(
                status_code=400,
                detail='Workout lines must use exercise|reps@weight, reps@weight|optional notes format.',
            )
        exercise_name = parts[0]
        notes = parts[2] if len(parts) > 2 and parts[2] else None
        sets = []
        for set_chunk in parts[1].split(','):
            token = set_chunk.strip()
            if not token:
                continue
            if '@' not in token:
                raise HTTPException(status_code=400, detail=f'Invalid set token: {token}')
            reps_raw, weight_raw = [value.strip() for value in token.split('@', 1)]
            try:
                reps = int(reps_raw)
                weight_lbs = float(weight_raw)
            except ValueError as exc:
                raise HTTPException(status_code=400, detail=f'Invalid set token: {token}') from exc
            sets.append({'reps': reps, 'weight_lbs': weight_lbs})
        if not sets:
            raise HTTPException(status_code=400, detail='Each workout exercise needs at least one set.')
        exercises.append({'exercise_name': exercise_name, 'notes': notes, 'sets': sets})
    if not exercises:
        raise HTTPException(status_code=400, detail='At least one workout exercise is required.')
    return exercises
