import urllib.request
import urllib.parse
import json
from typing import Optional

from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel, Field

from db import get_db_connection
from auth_routes import get_current_user
from astrology import calculate_planets


router = APIRouter(prefix="/user/personas", tags=["User Personas"])

RELATIONSHIP_OPTIONS = {"Partner", "Parent", "Child", "Friend", "Coworker", "Other"}


class PersonaRequest(BaseModel):
    name: str = Field(..., max_length=150)
    nickname: Optional[str] = Field(None, max_length=150)
    relationship: str

    birth_year: int
    birth_month: int
    birth_day: int

    birth_hour: Optional[int] = None
    birth_minute: Optional[int] = None
    birth_ampm: Optional[str] = None
    exact_birth_time: bool = True

    birth_location: str = Field(..., max_length=255)
    birth_lat: Optional[float] = None
    birth_lon: Optional[float] = None


def convert_to_24_hour(hour: int, minute: int, ampm: str):
    """
    Converts 12-hour AM/PM time to 24-hour time.

    Examples:
    12:00 AM -> 00:00
    12:00 PM -> 12:00
    02:30 PM -> 14:30
    """
    h = int(hour)
    m = int(minute)
    ampm_upper = ampm.upper().strip() if ampm else ""

    if ampm_upper == "AM":
        if h == 12:
            h = 0
    elif ampm_upper == "PM":
        if h != 12:
            h += 12

    return h, m


def geocode_location(location_name: str):
    """
    Geocodes a location string using Photon API.
    Returns: (latitude, longitude)
    """
    if not location_name or not location_name.strip():
        return None, None

    try:
        url = f"https://photon.komoot.io/api/?q={urllib.parse.quote(location_name)}&limit=1"

        req = urllib.request.Request(
            url,
            headers={"User-Agent": "Mozilla/5.0"}
        )

        with urllib.request.urlopen(req, timeout=5) as response:
            data = json.loads(response.read().decode())
            features = data.get("features", [])

            if features:
                coords = features[0].get("geometry", {}).get("coordinates", [])

                # Photon coordinates format: [longitude, latitude]
                if len(coords) >= 2:
                    lon = float(coords[0])
                    lat = float(coords[1])
                    return lat, lon

    except Exception as e:
        print(f"Error geocoding location '{location_name}': {e}")

    return None, None


def calculate_astrological_signs(
    year: int,
    month: int,
    day: int,
    hour: Optional[int],
    minute: Optional[int],
    ampm: Optional[str],
    exact_birth_time: bool,
    lat: Optional[float],
    lon: Optional[float]
):
    """
    Calculates Sun, Moon, and Rising signs.

    Important:
    - If exact birth time is unknown, we still calculate using default 12:00 PM.
    - Rising sign needs time + location, so default noon is used instead of returning None.
    """
    if lat is None or lon is None:
        print("Cannot calculate signs: latitude or longitude missing")
        return None, None, None

    # Default unknown birth time = 12:00 PM
    calc_hour = 12
    calc_minute = 0

    # Use provided time if available
    if hour is not None and minute is not None and ampm:
        calc_hour, calc_minute = convert_to_24_hour(hour, minute, ampm)

    try:
        result = calculate_planets(
            year=year,
            month=month,
            day=day,
            hour=calc_hour,
            minute=calc_minute,
            second=0,
            latitude=float(lat),
            longitude=float(lon),
            tz=0.0
        )

        planets = result.get("planets", {})
        ascendant = result.get("ascendant", {})

        sun_sign = planets.get("sun", {}).get("sign")
        moon_sign = planets.get("moon", {}).get("sign")
        rising_sign = ascendant.get("sign")

        print("Calculated persona signs:", {
            "date": f"{year}-{month}-{day}",
            "time": f"{calc_hour}:{calc_minute}",
            "lat": lat,
            "lon": lon,
            "sun": sun_sign,
            "moon": moon_sign,
            "rising": rising_sign,
            "exact_birth_time": exact_birth_time
        })

        return sun_sign, moon_sign, rising_sign

    except Exception as e:
        print(f"Error calculating signs: {e}")
        return None, None, None


def normalize_birth_time(req: PersonaRequest):
    """
    If exact birth time is unknown, save 12:00 PM.
    This keeps frontend display and backend calculation consistent.
    """
    if not req.exact_birth_time:
        req.birth_hour = 12
        req.birth_minute = 0
        req.birth_ampm = "PM"
        return

    if req.birth_hour is None or req.birth_minute is None or not req.birth_ampm:
        raise HTTPException(
            status_code=400,
            detail="Birth hour, minute, and AM/PM are required when exact_birth_time is true."
        )

    if req.birth_hour < 1 or req.birth_hour > 12:
        raise HTTPException(status_code=400, detail="Birth hour must be between 1 and 12.")

    if req.birth_minute < 0 or req.birth_minute > 59:
        raise HTTPException(status_code=400, detail="Birth minute must be between 0 and 59.")

    if req.birth_ampm.upper() not in {"AM", "PM"}:
        raise HTTPException(status_code=400, detail="Birth AM/PM must be 'AM' or 'PM'.")


def validate_relationship(relationship: str):
    if relationship not in RELATIONSHIP_OPTIONS:
        raise HTTPException(
            status_code=400,
            detail=f"Invalid relationship option. Choose from: {', '.join(RELATIONSHIP_OPTIONS)}"
        )


@router.get("")
def get_personas(current_user=Depends(get_current_user)):
    conn = get_db_connection()
    cursor = conn.cursor(dictionary=True)

    try:
        cursor.execute(
            """
            SELECT *
            FROM user_personas
            WHERE user_id = %s
            ORDER BY created_at DESC
            """,
            (current_user["id"],)
        )

        personas = cursor.fetchall()
        return personas

    finally:
        cursor.close()
        conn.close()


@router.post("")
def add_persona(req: PersonaRequest, current_user=Depends(get_current_user)):
    validate_relationship(req.relationship)

    # Unknown birth time becomes 12:00 PM
    normalize_birth_time(req)

    # If frontend did not send lat/lon, backend tries to geocode
    if req.birth_lat is None or req.birth_lon is None:
        req.birth_lat, req.birth_lon = geocode_location(req.birth_location)

    sun_sign, moon_sign, rising_sign = calculate_astrological_signs(
        year=req.birth_year,
        month=req.birth_month,
        day=req.birth_day,
        hour=req.birth_hour,
        minute=req.birth_minute,
        ampm=req.birth_ampm,
        exact_birth_time=req.exact_birth_time,
        lat=req.birth_lat,
        lon=req.birth_lon
    )

    conn = get_db_connection()
    cursor = conn.cursor(dictionary=True)

    try:
        query = """
            INSERT INTO user_personas (
                user_id,
                name,
                nickname,
                relationship,
                birth_year,
                birth_month,
                birth_day,
                birth_hour,
                birth_minute,
                birth_ampm,
                exact_birth_time,
                birth_location,
                birth_lat,
                birth_lon,
                sun_sign,
                moon_sign,
                rising_sign
            )
            VALUES (
                %s, %s, %s, %s,
                %s, %s, %s,
                %s, %s, %s,
                %s, %s,
                %s, %s,
                %s, %s, %s
            )
        """

        cursor.execute(
            query,
            (
                current_user["id"],
                req.name.strip(),
                req.nickname.strip() if req.nickname else None,
                req.relationship,
                req.birth_year,
                req.birth_month,
                req.birth_day,
                req.birth_hour,
                req.birth_minute,
                req.birth_ampm,
                req.exact_birth_time,
                req.birth_location,
                req.birth_lat,
                req.birth_lon,
                sun_sign,
                moon_sign,
                rising_sign
            )
        )

        conn.commit()
        persona_id = cursor.lastrowid

        cursor.execute(
            "SELECT * FROM user_personas WHERE id = %s AND user_id = %s",
            (persona_id, current_user["id"])
        )

        created_persona = cursor.fetchone()
        return created_persona

    finally:
        cursor.close()
        conn.close()


@router.put("/{persona_id}")
def update_persona(persona_id: int, req: PersonaRequest, current_user=Depends(get_current_user)):
    validate_relationship(req.relationship)

    conn = get_db_connection()
    cursor = conn.cursor(dictionary=True)

    try:
        cursor.execute(
            """
            SELECT id
            FROM user_personas
            WHERE id = %s AND user_id = %s
            """,
            (persona_id, current_user["id"])
        )

        persona = cursor.fetchone()

        if not persona:
            raise HTTPException(
                status_code=404,
                detail="Persona not found or you do not have permission to edit it."
            )

        # Unknown birth time becomes 12:00 PM
        normalize_birth_time(req)

        # If frontend did not send lat/lon, backend tries to geocode
        if req.birth_lat is None or req.birth_lon is None:
            req.birth_lat, req.birth_lon = geocode_location(req.birth_location)

        sun_sign, moon_sign, rising_sign = calculate_astrological_signs(
            year=req.birth_year,
            month=req.birth_month,
            day=req.birth_day,
            hour=req.birth_hour,
            minute=req.birth_minute,
            ampm=req.birth_ampm,
            exact_birth_time=req.exact_birth_time,
            lat=req.birth_lat,
            lon=req.birth_lon
        )

        update_query = """
            UPDATE user_personas
            SET
                name = %s,
                nickname = %s,
                relationship = %s,
                birth_year = %s,
                birth_month = %s,
                birth_day = %s,
                birth_hour = %s,
                birth_minute = %s,
                birth_ampm = %s,
                exact_birth_time = %s,
                birth_location = %s,
                birth_lat = %s,
                birth_lon = %s,
                sun_sign = %s,
                moon_sign = %s,
                rising_sign = %s
            WHERE id = %s AND user_id = %s
        """

        cursor.execute(
            update_query,
            (
                req.name.strip(),
                req.nickname.strip() if req.nickname else None,
                req.relationship,
                req.birth_year,
                req.birth_month,
                req.birth_day,
                req.birth_hour,
                req.birth_minute,
                req.birth_ampm,
                req.exact_birth_time,
                req.birth_location,
                req.birth_lat,
                req.birth_lon,
                sun_sign,
                moon_sign,
                rising_sign,
                persona_id,
                current_user["id"]
            )
        )

        conn.commit()

        cursor.execute(
            """
            SELECT *
            FROM user_personas
            WHERE id = %s AND user_id = %s
            """,
            (persona_id, current_user["id"])
        )

        updated_persona = cursor.fetchone()
        return updated_persona

    finally:
        cursor.close()
        conn.close()


@router.delete("/{persona_id}")
def delete_persona(persona_id: int, current_user=Depends(get_current_user)):
    conn = get_db_connection()
    cursor = conn.cursor(dictionary=True)

    try:
        cursor.execute(
            """
            SELECT id
            FROM user_personas
            WHERE id = %s AND user_id = %s
            """,
            (persona_id, current_user["id"])
        )

        persona = cursor.fetchone()

        if not persona:
            raise HTTPException(
                status_code=404,
                detail="Persona not found or you do not have permission to delete it."
            )

        cursor.execute(
            """
            DELETE FROM user_personas
            WHERE id = %s AND user_id = %s
            """,
            (persona_id, current_user["id"])
        )

        conn.commit()

        return {
            "success": True,
            "message": "Persona deleted successfully."
        }

    finally:
        cursor.close()
        conn.close()