"""API entry point for the trip optimization."""

import logging
import time
import yaml
from fastapi import HTTPException, APIRouter, BackgroundTasks, Body

from .optimizer import Optimizer
from .refiner import Refiner
from ..supabase.supabase import SupabaseClient
from ..structs.landmark import Landmark
from ..structs.preferences import Preferences
from ..structs.linked_landmarks import LinkedLandmarks
from ..structs.trip import Trip
from ..overpass.overpass import fill_cache
from ..cache import client as cache_client
from ..constants import OPTIMIZER_PARAMETERS_PATH


# Setup the Logger, Optimizer and Refiner
logger = logging.getLogger(__name__)
optimizer = Optimizer()
refiner = Refiner(optimizer=optimizer)
supabase = SupabaseClient()


# Initialize the API router
router = APIRouter()


@router.post("/optimize/trip")
def optimize_trip(
        user_id: str = Body(...),
        preferences: Preferences = Body(...),
        landmarks: list[Landmark] = Body(...),
        start: tuple[float, float] = Body(...),
        end: tuple[float, float] | None = Body(None),
        background_tasks: BackgroundTasks = None
    ) -> Trip:
    """
    Main function to call the optimizer.

    Args:
        preferences (Preferences)   : the preferences specified by the user as the post body.
        start (tuple[float, float]) : the coordinates of the starting point.
        end tuple[float, float]     : the coordinates of the finishing point.
        backgroud_tasks (BackgroundTasks) : necessary to fill the cache after the trip has been returned.
    Returns: 
        (uuid) : The uuid of the first landmark in the optimized route
    """
    # Check for valid user balance
    try:
        if not supabase.check_balance(user_id=user_id):
            logger.warning('Insufficient credits to perform this action.')
            raise HTTPException(status_code=418, detail='Insufficient credits')
    except SyntaxError as se :
        logger.error(f'SyntaxError: {se}')
        raise HTTPException(status_code=400, detail=str(se)) from se
    except ValueError as ve :
        logger.error(f'SyntaxError: {ve}')
        raise HTTPException(status_code=406, detail=str(ve)) from ve
    except Exception as exc:
        logger.error(f'SyntaxError: {exc}')
        raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(exc)}") from exc

    # Check for invalid input
    if preferences is None:
        raise HTTPException(status_code=406, detail="Preferences not provided or incomplete.")
    if len(landmarks) == 0 :
        raise HTTPException(status_code=406, detail="No landmarks provided for computing the trip.")
    if (preferences.shopping.score == 0 and
        preferences.sightseeing.score == 0 and
        preferences.nature.score == 0) :
        raise HTTPException(status_code=406, detail="All preferences are 0.")
    if start is None:
        raise HTTPException(status_code=406, detail="Start coordinates not provided")
    if not (-90 <= start[0] <= 90 or -180 <= start[1] <= 180):
        raise HTTPException(status_code=422, detail="Start coordinates not in range")
    if end is None:
        end = start
        logger.info("No end coordinates provided. Using start=end.")

    # Start the timer
    start_time = time.time()

    logger.info(f"Requested new trip generation. Details:\n\tCoordinates: {start}\n\tTime: {preferences.max_time_minute}\n\tSightseeing: {preferences.sightseeing.score}\n\tNature: {preferences.nature.score}\n\tShopping: {preferences.shopping.score}")

    start_landmark = Landmark(
        name='start',
        type='start',
        location=(start[0], start[1]),
        osm_type='start',
        osm_id=0,
        attractiveness=0,
        duration=0,
        must_do=True,
        n_tags = 0
    )

    end_landmark = Landmark(
        name='finish',
        type='finish',
        location=(end[0], end[1]),
        osm_type='end',
        osm_id=0,
        attractiveness=0,
        duration=0,
        must_do=True,
        n_tags=0
    )
    
    # From the parameters load the length at which to truncate the landmarks list.
    with OPTIMIZER_PARAMETERS_PATH.open('r') as f:
            parameters = yaml.safe_load(f)
            n_important = parameters['N_important']

    # Truncate to the  most important landmarks for a shorter list
    landmarks_short = landmarks[:n_important]

    # insert start and finish to the shorter landmarks list
    landmarks_short.insert(0, start_landmark)
    landmarks_short.append(end_landmark)

    # First stage optimization
    try:
        base_tour = optimizer.solve_optimization(preferences.max_time_minute, landmarks_short)
    except Exception as exc:
        logger.error(f"Trip generation failed: {str(exc)}")
        raise HTTPException(status_code=500, detail=f"Optimization failed: {str(exc)}") from exc

    t_first_stage = time.time() - start_time
    start_time = time.time()

    # Second stage optimization
    try :
        refined_tour = refiner.refine_optimization(
            landmarks, base_tour,
            preferences.max_time_minute,
            preferences.detour_tolerance_minute
        )
    except Exception as exc :
        logger.warning(f"Refiner failed. Proceeding with base trip {str(exc)}")
        refined_tour = base_tour

    t_second_stage = time.time() - start_time

    logger.debug(f'First stage optimization\t: {round(t_first_stage,3)} seconds')
    logger.debug(f'Second stage optimization\t: {round(t_second_stage,3)} seconds')
    logger.info(f'Total computation time\t: {round(t_first_stage + t_second_stage,3)} seconds')
    linked_tour = LinkedLandmarks(refined_tour)

    # upon creation of the trip, persistence of both the trip and its landmarks is ensured.
    trip = Trip.from_linked_landmarks(linked_tour, cache_client)
    logger.info(f'Optimized a trip of {trip.total_time} minutes with {len(refined_tour)} landmarks in {round(t_first_stage + t_second_stage,3)} seconds.')
    logger.info('Detailed trip :\n\t' + '\n\t'.join(f'{landmark}' for landmark in refined_tour))

    # Add the cache fill as background task
    background_tasks.add_task(fill_cache)

    # Finally, decrement the user balance
    supabase.decrement_credit_balance(user_id=user_id)

    return trip

