use std::fmt::Write as _;
use tokio_postgres::Row;

use super::NonNullTableAlias;
use super::NullableTableAlias;
use crate::entity::Entity;
use crate::entity::EntityId;

pub trait QuerySelectable {
    type SelectedType;

    fn select_columns(&self, out: &mut String);
    fn num_columns() -> usize; // NOTE: There must be at least 1 column every time
    fn convert_row(row: &Row, offset: usize) -> Self::SelectedType;
}

impl<E: Entity> QuerySelectable for &NonNullTableAlias<E> {
    type SelectedType = E;

    fn select_columns(&self, out: &mut String) {
        write!(out, "{}.{}", self.alias, E::COLUMN_NAMES[0]).unwrap();
        for col in &E::COLUMN_NAMES[1..] {
            write!(out, ", {}.{}", self.alias, col).unwrap();
        }
    }

    fn num_columns() -> usize {
        return E::COLUMN_NAMES.len();
    }

    fn convert_row(row: &Row, offset: usize) -> E {
        return E::from_db_row(row, offset);
    }
}

impl<E: Entity> QuerySelectable for &NullableTableAlias<E> {
    type SelectedType = Option<E>;

    fn select_columns(&self, out: &mut String) {
        write!(out, "{}.{}", self.alias, E::COLUMN_NAMES[0]).unwrap();
        for col in &E::COLUMN_NAMES[1..] {
            write!(out, ", {}.{}", self.alias, col).unwrap();
        }
    }

    fn num_columns() -> usize {
        return E::COLUMN_NAMES.len();
    }

    fn convert_row(row: &Row, offset: usize) -> Option<E> {
        let id = row.get::<_, Option<EntityId<E::EntityIdType>>>(offset + E::ID_COLUMN.index);
        return match id {
            Some(_) => Some(E::from_db_row(row, offset)),
            None => None,
        };
    }
}

impl<T1, T2> QuerySelectable for (T1, T2)
where
    T1: QuerySelectable,
    T2: QuerySelectable,
{
    type SelectedType = (T1::SelectedType, T2::SelectedType);

    fn select_columns(&self, out: &mut String) {
        self.0.select_columns(out);
        out.write_str(", ").unwrap();
        self.1.select_columns(out);
    }

    fn num_columns() -> usize {
        return T1::num_columns() + T2::num_columns();
    }

    fn convert_row(row: &Row, offset: usize) -> Self::SelectedType {
        let t1 = T1::convert_row(row, offset);
        let offset = offset + T1::num_columns();
        let t2 = T2::convert_row(row, offset);

        return (t1, t2);
    }
}

impl<T1, T2, T3> QuerySelectable for (T1, T2, T3)
where
    T1: QuerySelectable,
    T2: QuerySelectable,
    T3: QuerySelectable,
{
    type SelectedType = (T1::SelectedType, T2::SelectedType, T3::SelectedType);
    fn select_columns(&self, out: &mut String) {
        self.0.select_columns(out);
        out.write_str(", ").unwrap();
        self.1.select_columns(out);
        out.write_str(", ").unwrap();
        self.2.select_columns(out);
    }

    fn num_columns() -> usize {
        return T1::num_columns() + T2::num_columns() + T3::num_columns();
    }

    fn convert_row(row: &Row, offset: usize) -> Self::SelectedType {
        let t1 = T1::convert_row(row, offset);
        let offset = offset + T1::num_columns();
        let t2 = T2::convert_row(row, offset);
        let offset = offset + T2::num_columns();
        let t3 = T3::convert_row(row, offset);

        return (t1, t2, t3);
    }
}

impl<T1, T2, T3, T4> QuerySelectable for (T1, T2, T3, T4)
where
    T1: QuerySelectable,
    T2: QuerySelectable,
    T3: QuerySelectable,
    T4: QuerySelectable,
{
    type SelectedType = (
        T1::SelectedType,
        T2::SelectedType,
        T3::SelectedType,
        T4::SelectedType,
    );

    fn select_columns(&self, out: &mut String) {
        self.0.select_columns(out);
        out.write_str(", ").unwrap();
        self.1.select_columns(out);
        out.write_str(", ").unwrap();
        self.2.select_columns(out);
        out.write_str(", ").unwrap();
        self.3.select_columns(out);
    }

    fn num_columns() -> usize {
        return T1::num_columns() + T2::num_columns() + T3::num_columns() + T4::num_columns();
    }

    fn convert_row(row: &Row, offset: usize) -> Self::SelectedType {
        let t1 = T1::convert_row(row, offset);
        let offset = offset + T1::num_columns();
        let t2 = T2::convert_row(row, offset);
        let offset = offset + T2::num_columns();
        let t3 = T3::convert_row(row, offset);
        let offset = offset + T3::num_columns();
        let t4 = T4::convert_row(row, offset);

        return (t1, t2, t3, t4);
    }
}

impl<T1, T2, T3, T4, T5> QuerySelectable for (T1, T2, T3, T4, T5)
where
    T1: QuerySelectable,
    T2: QuerySelectable,
    T3: QuerySelectable,
    T4: QuerySelectable,
    T5: QuerySelectable,
{
    type SelectedType = (
        T1::SelectedType,
        T2::SelectedType,
        T3::SelectedType,
        T4::SelectedType,
        T5::SelectedType,
    );

    fn select_columns(&self, out: &mut String) {
        self.0.select_columns(out);
        out.write_str(", ").unwrap();
        self.1.select_columns(out);
        out.write_str(", ").unwrap();
        self.2.select_columns(out);
        out.write_str(", ").unwrap();
        self.3.select_columns(out);
        out.write_str(", ").unwrap();
        self.4.select_columns(out);
    }

    fn num_columns() -> usize {
        return T1::num_columns()
            + T2::num_columns()
            + T3::num_columns()
            + T4::num_columns()
            + T5::num_columns();
    }

    fn convert_row(row: &Row, offset: usize) -> Self::SelectedType {
        let t1 = T1::convert_row(row, offset);
        let offset = offset + T1::num_columns();
        let t2 = T2::convert_row(row, offset);
        let offset = offset + T2::num_columns();
        let t3 = T3::convert_row(row, offset);
        let offset = offset + T3::num_columns();
        let t4 = T4::convert_row(row, offset);
        let offset = offset + T4::num_columns();
        let t5 = T5::convert_row(row, offset);

        return (t1, t2, t3, t4, t5);
    }
}

impl<T1, T2, T3, T4, T5, T6> QuerySelectable for (T1, T2, T3, T4, T5, T6)
where
    T1: QuerySelectable,
    T2: QuerySelectable,
    T3: QuerySelectable,
    T4: QuerySelectable,
    T5: QuerySelectable,
    T6: QuerySelectable,
{
    type SelectedType = (
        T1::SelectedType,
        T2::SelectedType,
        T3::SelectedType,
        T4::SelectedType,
        T5::SelectedType,
        T6::SelectedType,
    );

    fn select_columns(&self, out: &mut String) {
        self.0.select_columns(out);
        out.write_str(", ").unwrap();
        self.1.select_columns(out);
        out.write_str(", ").unwrap();
        self.2.select_columns(out);
        out.write_str(", ").unwrap();
        self.3.select_columns(out);
        out.write_str(", ").unwrap();
        self.4.select_columns(out);
        out.write_str(", ").unwrap();
        self.5.select_columns(out);
    }

    fn num_columns() -> usize {
        return T1::num_columns()
            + T2::num_columns()
            + T3::num_columns()
            + T4::num_columns()
            + T5::num_columns()
            + T6::num_columns();
    }

    fn convert_row(row: &Row, offset: usize) -> Self::SelectedType {
        let t1 = T1::convert_row(row, offset);
        let offset = offset + T1::num_columns();
        let t2 = T2::convert_row(row, offset);
        let offset = offset + T2::num_columns();
        let t3 = T3::convert_row(row, offset);
        let offset = offset + T3::num_columns();
        let t4 = T4::convert_row(row, offset);
        let offset = offset + T4::num_columns();
        let t5 = T5::convert_row(row, offset);
        let offset = offset + T5::num_columns();
        let t6 = T6::convert_row(row, offset);

        return (t1, t2, t3, t4, t5, t6);
    }
}

impl<T1, T2, T3, T4, T5, T6, T7> QuerySelectable for (T1, T2, T3, T4, T5, T6, T7)
where
    T1: QuerySelectable,
    T2: QuerySelectable,
    T3: QuerySelectable,
    T4: QuerySelectable,
    T5: QuerySelectable,
    T6: QuerySelectable,
    T7: QuerySelectable,
{
    type SelectedType = (
        T1::SelectedType,
        T2::SelectedType,
        T3::SelectedType,
        T4::SelectedType,
        T5::SelectedType,
        T6::SelectedType,
        T7::SelectedType,
    );

    fn select_columns(&self, out: &mut String) {
        self.0.select_columns(out);
        out.write_str(", ").unwrap();
        self.1.select_columns(out);
        out.write_str(", ").unwrap();
        self.2.select_columns(out);
        out.write_str(", ").unwrap();
        self.3.select_columns(out);
        out.write_str(", ").unwrap();
        self.4.select_columns(out);
        out.write_str(", ").unwrap();
        self.5.select_columns(out);
        out.write_str(", ").unwrap();
        self.6.select_columns(out);
    }

    fn num_columns() -> usize {
        return T1::num_columns()
            + T2::num_columns()
            + T3::num_columns()
            + T4::num_columns()
            + T5::num_columns()
            + T6::num_columns()
            + T7::num_columns();
    }

    fn convert_row(row: &Row, offset: usize) -> Self::SelectedType {
        let t1 = T1::convert_row(row, offset);
        let offset = offset + T1::num_columns();
        let t2 = T2::convert_row(row, offset);
        let offset = offset + T2::num_columns();
        let t3 = T3::convert_row(row, offset);
        let offset = offset + T3::num_columns();
        let t4 = T4::convert_row(row, offset);
        let offset = offset + T4::num_columns();
        let t5 = T5::convert_row(row, offset);
        let offset = offset + T5::num_columns();
        let t6 = T6::convert_row(row, offset);
        let offset = offset + T6::num_columns();
        let t7 = T7::convert_row(row, offset);

        return (t1, t2, t3, t4, t5, t6, t7);
    }
}

impl<T1, T2, T3, T4, T5, T6, T7, T8> QuerySelectable for (T1, T2, T3, T4, T5, T6, T7, T8)
where
    T1: QuerySelectable,
    T2: QuerySelectable,
    T3: QuerySelectable,
    T4: QuerySelectable,
    T5: QuerySelectable,
    T6: QuerySelectable,
    T7: QuerySelectable,
    T8: QuerySelectable,
{
    type SelectedType = (
        T1::SelectedType,
        T2::SelectedType,
        T3::SelectedType,
        T4::SelectedType,
        T5::SelectedType,
        T6::SelectedType,
        T7::SelectedType,
        T8::SelectedType,
    );

    fn select_columns(&self, out: &mut String) {
        self.0.select_columns(out);
        out.write_str(", ").unwrap();
        self.1.select_columns(out);
        out.write_str(", ").unwrap();
        self.2.select_columns(out);
        out.write_str(", ").unwrap();
        self.3.select_columns(out);
        out.write_str(", ").unwrap();
        self.4.select_columns(out);
        out.write_str(", ").unwrap();
        self.5.select_columns(out);
        out.write_str(", ").unwrap();
        self.6.select_columns(out);
        out.write_str(", ").unwrap();
        self.7.select_columns(out);
    }

    fn num_columns() -> usize {
        return T1::num_columns()
            + T2::num_columns()
            + T3::num_columns()
            + T4::num_columns()
            + T5::num_columns()
            + T6::num_columns()
            + T7::num_columns()
            + T8::num_columns();
    }

    fn convert_row(row: &Row, offset: usize) -> Self::SelectedType {
        let t1 = T1::convert_row(row, offset);
        let offset = offset + T1::num_columns();
        let t2 = T2::convert_row(row, offset);
        let offset = offset + T2::num_columns();
        let t3 = T3::convert_row(row, offset);
        let offset = offset + T3::num_columns();
        let t4 = T4::convert_row(row, offset);
        let offset = offset + T4::num_columns();
        let t5 = T5::convert_row(row, offset);
        let offset = offset + T5::num_columns();
        let t6 = T6::convert_row(row, offset);
        let offset = offset + T6::num_columns();
        let t7 = T7::convert_row(row, offset);
        let offset = offset + T7::num_columns();
        let t8 = T8::convert_row(row, offset);

        return (t1, t2, t3, t4, t5, t6, t7, t8);
    }
}
