"""Main app for backend api"""

import logging
from fastapi import FastAPI, HTTPException

from .structs.landmark import Landmark
from .structs.preferences import Preferences
from .structs.linked_landmarks import LinkedLandmarks
from .structs.trip import Trip
from .utils.landmarks_manager import LandmarkManager
from .utils.optimizer import Optimizer
from .utils.refiner import Refiner
from .persistence import client as cache_client


logger = logging.getLogger(__name__)

app = FastAPI()
manager = LandmarkManager()
optimizer = Optimizer()
refiner = Refiner(optimizer=optimizer)


@app.post("/trip/new")
def new_trip(preferences: Preferences,
             start: tuple[float, float],
             end: tuple[float, float] | None = None) -> Trip:
    """
    Main function to call the optimizer.

    Args:
        preferences : the preferences specified by the user as the post body
        start       : the coordinates of the starting point
        end         : the coordinates of the finishing point
    Returns: 
        (uuid) : The uuid of the first landmark in the optimized route
    """
    if preferences is None:
        raise HTTPException(status_code=406,
                            detail="Preferences not provided or incomplete.")
    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_landmark = Landmark(name='start',
                              type='start',
                              location=(start[0], start[1]),
                              osm_type='start',
                              osm_id=0,
                              attractiveness=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,
                            must_do=True,
                            n_tags = 0)

    # Generate the landmarks from the start location
    landmarks, landmarks_short = manager.generate_landmarks_list(
        center_coordinates = start,
        preferences = preferences
    )

    # insert start and finish to the 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 ArithmeticError as exc:
        raise HTTPException(status_code=500, detail="No solution found") from exc
    except TimeoutError as exc:
        raise HTTPException(status_code=500, detail="Optimzation took too long") from exc

    # Second stage optimization
    refined_tour = refiner.refine_optimization(landmarks, base_tour,
                                               preferences.max_time_minute,
                                               preferences.detour_tolerance_minute)

    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)
    return trip


#### For already existing trips/landmarks
@app.get("/trip/{trip_uuid}")
def get_trip(trip_uuid: str) -> Trip:
    """
    Look-up the cache for a trip that has been previously generated using its identifier.

    Args:
        trip_uuid (str) : unique identifier for a trip.

    Returns:
        (Trip)          : the corresponding trip.
    """
    try:
        trip = cache_client.get(f"trip_{trip_uuid}")
        return trip
    except KeyError as exc:
        raise HTTPException(status_code=404, detail="Trip not found") from exc


@app.get("/landmark/{landmark_uuid}")
def get_landmark(landmark_uuid: str) -> Landmark:
    """
    Returns a Landmark from its unique identifier.

    Args:
        landmark_uuid (str) : unique identifier for a Landmark.

    Returns:
        (Landmark)          : the corresponding Landmark.
    """
    try:
        landmark = cache_client.get(f"landmark_{landmark_uuid}")
        return landmark
    except KeyError as exc:
        raise HTTPException(status_code=404,
                            detail="Landmark not found") from exc