use crate::utils::wiki::fandom_planet_url;
use rusqlite::{Result as SqlResult, Row};
use serde::{Deserialize, Serialize};

pub const PC_TO_LY: f64 = 3.26156;

#[derive(Debug)]
pub struct Planet {
    pub fid: i64,
    pub planet: String,
    pub planet_norm: String,
    pub region: Option<String>,
    pub sector: Option<String>,
    pub system: Option<String>,
    pub grid: Option<String>,
    pub x: f64,
    pub y: f64,
    pub canon: Option<i64>,
    pub legends: Option<i64>,
    pub zm: Option<i64>,
    pub name0: Option<String>,
    pub name1: Option<String>,
    pub name2: Option<String>,
    pub lat: Option<f64>,
    pub long: Option<f64>,
    pub reference: Option<String>,
    pub status: Option<String>,
    pub c_region: Option<String>,
    pub c_region_li: Option<String>,
}

#[derive(Debug)]
pub struct AliasRow {
    pub alias: String,
    pub source: Option<String>,
}

#[derive(Debug, Clone)]
pub struct NearHit {
    pub fid: i64,
    pub planet: String,
    pub x: f64,
    pub y: f64,
    pub distance: f64,
}

#[derive(Debug, Clone)]
pub struct UnknownPlanet {
    pub id: i64,
    pub fid: Option<i64>,
    pub planet: String,
    pub planet_norm: String,
    pub region: Option<String>,
    pub sector: Option<String>,
    pub system: Option<String>,
    pub grid: Option<String>,
    pub x: Option<f64>,
    pub y: Option<f64>,
    pub arcgis_hash: Option<String>,
    pub deleted: i64,
    pub canon: Option<i64>,
    pub legends: Option<i64>,
    pub zm: Option<i64>,
    pub name0: Option<String>,
    pub name1: Option<String>,
    pub name2: Option<String>,
    pub lat: Option<f64>,
    pub long: Option<f64>,
    pub reference: Option<String>,
    pub status: Option<String>,
    pub c_region: Option<String>,
    pub c_region_li: Option<String>,
    pub reason: Option<String>,
    pub reviewed: i64,
    pub promoted: i64,
    pub notes: Option<String>,
}

#[derive(Debug)]
pub struct Waypoint {
    pub id: i64,
    pub name: String,
    pub name_norm: String,
    pub x: f64,
    pub y: f64,
    pub kind: String,
    pub fingerprint: Option<String>,
    pub note: Option<String>,
    pub created_at: String,
    pub updated_at: Option<String>,
}

#[derive(Debug)]
pub struct WaypointListRow {
    pub waypoint: Waypoint,
    pub links_count: i64,
    pub routes_count: i64, // NEW
}

#[derive(Debug)]
pub struct WaypointLinkRow {
    pub planet_fid: i64,
    pub planet_name: String,
    pub role: String,
    pub distance: Option<f64>,
}

#[derive(Debug)]
pub struct WaypointRouteRow {
    pub id: i64,
    pub from_planet_fid: i64,
    pub from_planet_name: String,
    pub to_planet_fid: i64,
    pub to_planet_name: String,
    pub status: String,
    pub length: Option<f64>,
    pub updated_at: String,
    pub occurrences: i64, // quante volte compare quel waypoint nella polyline
}
impl Waypoint {
    pub fn fmt_short(&self) -> String {
        format!(
            "#{:<4} {:<24} ({:>10.3}, {:>10.3}) kind={}",
            self.id, self.name, self.x, self.y, self.kind
        )
    }
}

#[allow(dead_code)]
#[derive(Debug)]
pub struct WaypointPlanetLink {
    pub waypoint_id: i64,
    pub planet_fid: i64,
    pub role: String,
    pub distance: Option<f64>,
}

impl Planet {
    pub fn from_row(r: &Row<'_>) -> SqlResult<Self> {
        Ok(Self {
            fid: r.get("fid")?,
            planet: r.get("planet")?,
            planet_norm: r.get("planet_norm")?,
            region: r.get("region")?,
            sector: r.get("sector")?,
            system: r.get("system")?,
            grid: r.get("grid")?,
            x: r.get("x")?,
            y: r.get("y")?,
            canon: r.get("canon")?,
            legends: r.get("legends")?,
            zm: r.get("zm")?,
            name0: r.get("name0")?,
            name1: r.get("name1")?,
            name2: r.get("name2")?,
            lat: r.get("lat")?,
            long: r.get("long")?,
            reference: r.get("reference")?,
            status: r.get("status")?,
            c_region: r.get("c_region")?,
            c_region_li: r.get("c_region_li")?,
        })
    }

    pub fn info_planet_url(&self) -> String {
        fandom_planet_url(&self.planet)
    }
}

#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct RouteRow {
    pub id: i64,
    pub from_planet_fid: i64,
    pub to_planet_fid: i64,
    pub from_planet_name: String,
    pub to_planet_name: String,
    pub algo_version: String,
    pub options_json: String,
    pub length: Option<f64>,
    pub iterations: Option<i64>,
    pub status: String,
    pub error: Option<String>,
    pub created_at: String,
    pub updated_at: Option<String>,
}

#[derive(Debug, Clone)]
pub struct RouteWaypointRow {
    pub seq: i64,
    pub x: f64,
    pub y: f64,
    pub waypoint_id: Option<i64>,
    // NEW (nullable because start/end may have no waypoint_id)
    pub waypoint_name: Option<String>,
    pub waypoint_kind: Option<String>,
}

#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct RouteDetourRow {
    pub idx: i64,

    pub iteration: i64,
    pub segment_index: i64,

    pub obstacle_id: i64,
    pub obstacle_name: String,
    pub obstacle_x: f64,
    pub obstacle_y: f64,
    pub obstacle_radius: f64,

    pub closest_t: f64,
    pub closest_qx: f64,
    pub closest_qy: f64,
    pub closest_dist: f64,

    pub offset_used: f64,

    pub wp_x: f64,
    pub wp_y: f64,
    pub waypoint_id: Option<i64>,

    pub score_base: f64,
    pub score_turn: f64,
    pub score_back: f64,
    pub score_proximity: f64,
    pub score_total: f64,

    pub tries_used: Option<i64>,
    pub tries_exhausted: i64,
}

#[derive(Debug, Clone)]
pub struct RouteLoaded {
    pub route: RouteRow,
    pub waypoints: Vec<RouteWaypointRow>,
    pub detours: Vec<RouteDetourRow>,
}

/// A lightweight view of an obstacle used by the routing engine.
///
/// We use a named struct rather than a large tuple to keep the API readable and
/// to avoid triggering `clippy::type_complexity` when `-D warnings` is enabled.
#[derive(Debug, Clone)]
pub struct RoutingObstacleRow {
    pub fid: i64,
    pub planet: String,
    pub x: f64,
    pub y: f64,
    pub radius: f64,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RouteOptionsJson {
    pub clearance: f64,
    pub max_iters: usize,
    pub max_offset_tries: usize,
    pub offset_growth: f64,
    pub turn_weight: f64,
    pub back_weight: f64,
    pub proximity_weight: f64,
    pub proximity_margin: f64,
}

#[derive(Debug, Clone)]
pub struct RouteListRow {
    pub id: i64,
    pub from_planet_fid: i64,
    pub from_planet_name: String,
    pub to_planet_fid: i64,
    pub to_planet_name: String,

    pub status: String,
    pub length: Option<f64>,
    pub iterations: Option<i64>,

    pub created_at: String,
    pub updated_at: Option<String>,

    pub waypoints_count: i64,
    pub detours_count: i64,
}

#[derive(Debug, Clone)]
pub struct PlanetSearchRow {
    pub fid: i64,
    pub name: String,
    pub region: Option<String>,
    pub sector: Option<String>,
    pub system: Option<String>,
    pub grid: Option<String>,
    pub x: f64,
    pub y: f64,
    pub canon: bool,
    pub legends: bool,
    pub status: Option<String>,
}

/// Filter criteria for combined planet search.
///
/// At least one field must be set (query or any filter).
/// All active filters are combined with AND.
#[derive(Debug, Clone, Default)]
pub struct SearchFilter {
    /// Text query on planet name / alias (FTS or LIKE).
    pub query: Option<String>,
    /// Region filter (LIKE, case-insensitive).
    pub region: Option<String>,
    /// Sector filter (LIKE, case-insensitive).
    pub sector: Option<String>,
    /// Grid filter (exact, case-insensitive).
    pub grid: Option<String>,
    /// Status filter (exact: active, inserted, modified, skipped, deleted).
    pub status: Option<String>,
    /// If true, only Canon planets.
    pub canon: Option<bool>,
    /// If true, only Legends planets.
    pub legends: Option<bool>,
    /// Enable fuzzy matching (Levenshtein distance tolerance for typos).
    pub fuzzy: bool,
    /// Max results.
    pub limit: i64,
}

/// Aggregate galaxy statistics.
#[derive(Debug, Clone, Default)]
pub struct GalaxyStats {
    pub total_planets: i64,

    // By status
    pub status_active: i64,
    pub status_inserted: i64,
    pub status_modified: i64,
    pub status_skipped: i64,
    pub status_deleted: i64,
    pub status_invalid: i64,
    pub status_null: i64,

    // Canon / Legends
    pub canon_count: i64,
    pub legends_count: i64,
    pub both_count: i64,
    pub neither_count: i64,

    // Top regions (name, count)
    pub top_regions: Vec<(String, i64)>,
    // Top sectors (name, count)
    pub top_sectors: Vec<(String, i64)>,

    // Grid coverage
    pub distinct_grids: i64,
    pub top_grids: Vec<(String, i64)>,

    // Routes
    pub total_routes: i64,
    pub routes_ok: i64,
    pub routes_failed: i64,
    pub total_route_length: f64,
    pub avg_detours_per_route: f64,
}

/// Represents an unknown planet hit near a known planet.
#[derive(Debug, Clone)]
pub struct UnknownNearHit {
    pub id: i64,
    pub fid: Option<i64>,
    pub planet: String,
    pub x: Option<f64>,
    pub y: Option<f64>,
    pub reason: Option<String>,
    pub reviewed: i64,
    pub promoted: i64,
    pub distance: f64,
}
