use sea_query::{Expr, Func, Query, SelectStatement};

use shared::models::graphql::GetDriversOpts;
use shared::models::Driver as DriverModel;

use crate::{iden::*, one_of, sql::SqlBuilder};

pub struct DriverQueryBuilder<P> {
    stmt: SelectStatement,
    params: P,
}

impl DriverQueryBuilder<()> {
    pub fn driver(driver_ref: &str) -> Self {
        let stmt = Query::select()
            .distinct()
            .from(Drivers::Table)
            .columns(
                [
                    Drivers::DriverId,
                    Drivers::DriverRef,
                    Drivers::Number,
                    Drivers::Code,
                    Drivers::Forename,
                    Drivers::Surname,
                    Drivers::Dob,
                    Drivers::Nationality,
                    Drivers::Url,
                ]
                .into_iter()
                .map(|c| (Drivers::Table, c)),
            )
            .and_where(Expr::col((Drivers::Table, Drivers::DriverRef)).eq(Expr::value(driver_ref)))
            .to_owned();

        Self { stmt, params: () }
    }
}

impl DriverQueryBuilder<GetDriversOpts> {
    pub fn drivers(params: GetDriversOpts) -> Self {
        let stmt = Query::select()
            .distinct()
            .from(Drivers::Table)
            .columns(
                [
                    Drivers::DriverId,
                    Drivers::DriverRef,
                    Drivers::Number,
                    Drivers::Code,
                    Drivers::Forename,
                    Drivers::Surname,
                    Drivers::Dob,
                    Drivers::Nationality,
                    Drivers::Url,
                ]
                .into_iter()
                .map(|c| (Drivers::Table, c)),
            )
            .to_owned();

        Self { stmt, params }.build()
    }

    fn one_of(&self) -> bool {
        self.params.year.is_some()
            || self.params.constructor_ref.is_some()
            || self.params.status.is_some()
            || self.params.grid.is_some()
            || self.params.result.is_some()
            || self.params.circuit_ref.is_some()
            || self.params.fastest.is_some()
    }

    fn build(self) -> Self {
        self.from(
            |s| {
                one_of!(
                    s.params.year,
                    s.params.circuit_ref,
                    s.params.driver_standing
                )
            },
            Races::Table,
        )
        .from(Self::one_of, Results::Table)
        .from(|s| s.params.circuit_ref.is_some(), Circuits::Table)
        .from(|s| s.params.constructor_ref.is_some(), Constructors::Table)
        .from(
            |s| s.params.driver_standing.is_some(),
            DriverStandings::Table,
        )
        .and_clause_one()
        .and_clause_two()
        .and_where(|s| {
            s.params
                .year
                .map(|year| Expr::col((Races::Table, Races::Year)).eq(Expr::value(year)))
        })
        .and_clause_three()
    }

    fn and_clause_one(self) -> Self {
        if self.params.driver_standing.is_none() {
            return self;
        }

        self.and_where(|s| {
            if one_of!(s.params.year, s.params.constructor_ref) {
                Some(
                    Expr::col((Drivers::Table, Drivers::DriverId))
                        .equals((Results::Table, Results::DriverId)),
                )
            } else {
                None
            }
        })
        .and_where(|s| {
            s.params.year.map(|_| {
                Expr::col((Results::Table, Results::RaceId)).equals((Races::Table, Races::RaceId))
            })
        })
        .and_where(|s| {
            s.params.constructor_ref.as_ref().map(|constructor_ref| {
                Expr::col((Results::Table, Results::ConstructorId))
                    .equals((Constructors::Table, Constructors::ConstructorId))
                    .and(
                        Expr::col((Constructors::Table, Constructors::ConstructorRef))
                            .eq(Expr::value(&**constructor_ref)),
                    )
            })
        })
        .and_where(|s| {
            s.params.driver_standing.as_ref().map(|driver_standings| {
                Expr::col((DriverStandings::Table, DriverStandings::PositionText))
                    .eq(Expr::value(*driver_standings))
            })
        })
        .and_where(|_| {
            Some(
                Expr::col((DriverStandings::Table, DriverStandings::RaceId))
                    .equals((Races::Table, Races::RaceId)),
            )
        })
        .and_where(|_| {
            Some(
                Expr::col((DriverStandings::Table, DriverStandings::DriverId))
                    .equals((Drivers::Table, Drivers::DriverId)),
            )
        })
    }

    fn and_clause_two(self) -> Self {
        if self.params.driver_standing.is_some() {
            return self;
        }

        self.and_where(|s| {
            if s.one_of() {
                Some(
                    Expr::col((Drivers::Table, Drivers::DriverId))
                        .equals((Results::Table, Results::DriverId)),
                )
            } else {
                None
            }
        })
        .and_where(|s| {
            if s.params.year.is_some() || s.params.circuit_ref.is_some() {
                Some(
                    Expr::col((Results::Table, Results::RaceId))
                        .equals((Races::Table, Races::RaceId)),
                )
            } else {
                None
            }
        })
        .and_where(|s| {
            s.params.circuit_ref.as_ref().map(|circuit_ref| {
                Expr::col((Races::Table, Races::CircuitId))
                    .equals((Circuits::Table, Circuits::CircuitId))
                    .and(
                        Expr::col((Circuits::Table, Circuits::CircuitRef))
                            .eq(Expr::value(&**circuit_ref)),
                    )
            })
        })
        .and_where(|s| {
            s.params.constructor_ref.as_ref().map(|constructor_ref| {
                Expr::col((Results::Table, Results::ConstructorId))
                    .equals((Constructors::Table, Constructors::ConstructorId))
                    .and(
                        Expr::col((Constructors::Table, Constructors::ConstructorRef))
                            .eq(Expr::value(&**constructor_ref)),
                    )
            })
        })
        .and_where(|s| {
            s.params.status.map(|status| {
                Expr::col((Results::Table, Results::StatusId)).eq(Expr::value(status))
            })
        })
        .and_where(|s| {
            s.params
                .grid
                .map(|grid| Expr::col((Results::Table, Results::Grid)).eq(Expr::value(grid)))
        })
        .and_where(|s| {
            s.params
                .fastest
                .map(|fastest| Expr::col((Results::Table, Results::Rank)).eq(Expr::value(fastest)))
        })
        .and_where(|s| {
            s.params.result.map(|result| {
                Expr::col((Results::Table, Results::PositionText)).eq(Expr::value(result))
            })
        })
    }

    fn and_clause_three(self) -> Self {
        if let Some(round) = self.params.round {
            return self.and_where(|_| {
                Some(Expr::col((Races::Table, Races::Round)).eq(Expr::value(round)))
            });
        }

        if self.params.driver_standing.is_some() {
            if let Some(year) = self.params.year {
                return self.and_where(|_| {
                    Some(
                        Expr::col((Races::Table, Races::Round)).in_subquery(
                            Query::select()
                                .expr(Func::max(Expr::col(Races::Round)))
                                .from(Races::Table)
                                .and_where(
                                    Expr::col((Races::Table, Races::Year)).eq(Expr::value(year)),
                                )
                                .to_owned(),
                        ),
                    )
                });
            } else {
                return self.and_where(|_| {
                    Some(
                        Expr::tuple(
                            [
                                Expr::col((Races::Table, Races::Year)),
                                Expr::col((Races::Table, Races::Round)),
                            ]
                            .map(Into::into),
                        )
                        .in_subquery(
                            Query::select()
                                .column(Races::Year)
                                .expr(Func::max(Expr::col(Races::Round)))
                                .from(Races::Table)
                                .group_by_col(Races::Year)
                                .to_owned(),
                        ),
                    )
                });
            }
        }

        self
    }
}

impl<P> SqlBuilder for DriverQueryBuilder<P> {
    type Output = DriverModel;

    fn stmt(&mut self) -> &mut sea_query::SelectStatement {
        &mut self.stmt
    }
}
