use std::collections::HashMap;
use std::convert::Into;
use serde::{Serialize, Deserialize};
use futures::TryStreamExt;
use libpso::character::{settings, guildcard};
use libpso::util::vec_to_array;
use crate::entity::account::*;
use crate::entity::character::*;
use crate::entity::gateway::EntityGateway;
use crate::entity::item::*;
use crate::ship::map::MapArea;

use sqlx::postgres::PgPoolOptions;
use sqlx::Row;
use sqlx::Execute;
use postgres::{Client, NoTls};


#[derive(Debug, sqlx::FromRow)]
pub struct PgUserAccount {
    id: i32,
    username: String,
    password: String,
    banned: Option<chrono::DateTime<chrono::Utc>>,
    muted: Option<chrono::DateTime<chrono::Utc>>,
    created_at: chrono::DateTime<chrono::Utc>,
    flags: i32,
}

impl Into<UserAccountEntity> for PgUserAccount {
    fn into(self) -> UserAccountEntity {
        UserAccountEntity {
            id: UserAccountId(self.id as u32),
            username: self.username,
            password: self.password,
            banned_until: self.banned,
            muted_until: self.muted,
            created_at: self.created_at,
            flags: self.flags as u32,
            // TOOD
            guildcard: self.id as u32 + 1,
            team_id: None,
        }
    }
}

#[derive(Debug, sqlx::FromRow)]
pub struct PgUserSettings {
    id: i32,
    user_account: i32,
    blocked_users: Vec<u8>, //[u32; 0x1E],
    key_config: Vec<u8>, //[u8; 0x16C],
    joystick_config: Vec<u8>, //[u8; 0x38],
    option_flags: i32,
    shortcuts: Vec<u8>, //[u8; 0xA40],
    symbol_chats: Vec<u8>, //[u8; 0x4E0],
    team_name: Vec<u8>, //[u16; 0x10],
}

impl Into<UserSettingsEntity> for PgUserSettings {
    fn into(self) -> UserSettingsEntity {
        UserSettingsEntity {
            id: UserSettingsId(self.id as u32),
            user_id: UserAccountId(self.user_account as u32),
            settings: settings::UserSettings {
                blocked_users: vec_to_array(self.blocked_users.chunks(4).map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]])).collect()),
                key_config: vec_to_array(self.key_config),
                joystick_config: vec_to_array(self.joystick_config),
                option_flags: self.option_flags as u32,
                shortcuts: vec_to_array(self.shortcuts),
                symbol_chats: vec_to_array(self.symbol_chats),
                team_name: vec_to_array(self.team_name.chunks(2).map(|b| u16::from_le_bytes([b[0], b[1]])).collect()),
            }
        }
    }
}

#[derive(sqlx::Type, Debug)]
#[sqlx(rename_all = "lowercase")]
pub enum PgCharacterClass {
    HUmar,
    HUnewearl,
    HUcast,
    HUcaseal,
    RAmar,
    RAmarl,
    RAcast,
    RAcaseal,
    FOmar,
    FOmarl,
    FOnewm,
    FOnewearl,
}

impl Into<CharacterClass> for PgCharacterClass {
    fn into(self) -> CharacterClass {
        match self {
            PgCharacterClass::HUmar => CharacterClass::HUmar,
            PgCharacterClass::HUnewearl => CharacterClass::HUnewearl,
            PgCharacterClass::HUcast => CharacterClass::HUcast,
            PgCharacterClass::HUcaseal => CharacterClass::HUcaseal,
            PgCharacterClass::RAmar => CharacterClass::RAmar,
            PgCharacterClass::RAmarl => CharacterClass::RAmarl,
            PgCharacterClass::RAcast => CharacterClass::RAcast,
            PgCharacterClass::RAcaseal => CharacterClass::RAcaseal,
            PgCharacterClass::FOmar => CharacterClass::FOmar,
            PgCharacterClass::FOmarl => CharacterClass::FOmarl,
            PgCharacterClass::FOnewm => CharacterClass::FOnewm,
            PgCharacterClass::FOnewearl => CharacterClass::FOnewearl,
        }
    }
}

impl From<CharacterClass> for PgCharacterClass {
    fn from(other: CharacterClass) -> PgCharacterClass {
        match other {
            CharacterClass::HUmar => PgCharacterClass::HUmar,
            CharacterClass::HUnewearl => PgCharacterClass::HUnewearl,
            CharacterClass::HUcast => PgCharacterClass::HUcast,
            CharacterClass::HUcaseal => PgCharacterClass::HUcaseal,
            CharacterClass::RAmar => PgCharacterClass::RAmar,
            CharacterClass::RAmarl => PgCharacterClass::RAmarl,
            CharacterClass::RAcast => PgCharacterClass::RAcast,
            CharacterClass::RAcaseal => PgCharacterClass::RAcaseal,
            CharacterClass::FOmar => PgCharacterClass::FOmar,
            CharacterClass::FOmarl => PgCharacterClass::FOmarl,
            CharacterClass::FOnewm => PgCharacterClass::FOnewm,
            CharacterClass::FOnewearl => PgCharacterClass::FOnewearl,
        }
    }
}

#[derive(sqlx::Type, Debug)]
#[sqlx(rename_all = "lowercase")]
pub enum PgSectionId {
    Viridia,
    Greenill,
    Skyly,
    Bluefull,
    Purplenum,
    Pinkal,
    Redria,
    Oran,
    Yellowboze,
    Whitill,
}

impl Into<SectionID> for PgSectionId {
    fn into(self) -> SectionID {
        match self {
            PgSectionId::Viridia => SectionID::Viridia,
            PgSectionId::Greenill => SectionID::Greenill,
            PgSectionId::Skyly => SectionID::Skyly,
            PgSectionId::Bluefull => SectionID::Bluefull,
            PgSectionId::Purplenum => SectionID::Purplenum,
            PgSectionId::Pinkal => SectionID::Pinkal,
            PgSectionId::Redria => SectionID::Redria,
            PgSectionId::Oran => SectionID::Oran,
            PgSectionId::Yellowboze => SectionID::Yellowboze,
            PgSectionId::Whitill => SectionID::Whitill,
        }
    }
}

impl From<SectionID> for PgSectionId {
    fn from(other: SectionID) -> PgSectionId {
        match other {
            SectionID::Viridia => PgSectionId::Viridia,
            SectionID::Greenill => PgSectionId::Greenill,
            SectionID::Skyly => PgSectionId::Skyly,
            SectionID::Bluefull => PgSectionId::Bluefull,
            SectionID::Purplenum => PgSectionId::Purplenum,
            SectionID::Pinkal => PgSectionId::Pinkal,
            SectionID::Redria => PgSectionId::Redria,
            SectionID::Oran => PgSectionId::Oran,
            SectionID::Yellowboze => PgSectionId::Yellowboze,
            SectionID::Whitill => PgSectionId::Whitill,
        }
    }
}


#[derive(Debug, sqlx::FromRow)]
pub struct PgCharacter {
    pub id: i32,
    user_account: i32,
    pub slot: i16,
    name: String,
    exp: i32,
    class: String,
    section_id: String,

    costume: i16,
    skin: i16,
    face: i16,
    head: i16,
    hair: i16,
    hair_r: i16,
    hair_g: i16,
    hair_b: i16,
    prop_x: f32,
    prop_y: f32,

    techs: Vec<u8>,
    
    config: Vec<u8>,
    infoboard: String,
    guildcard: String,
    option_flags: i32,
    
    power: i16,
    mind: i16,
    def: i16,
    evade: i16,
    luck: i16,
    hp: i16,
    tp: i16,

    tech_menu: Vec<u8>,
    meseta: i32,
    bank_meseta: i32,
}

impl Into<CharacterEntity> for PgCharacter {
    fn into(self) -> CharacterEntity {
        CharacterEntity {
            id: CharacterEntityId(self.id as u32),
            user_id: UserAccountId(self.user_account as u32),
            slot: self.slot as u32,
            name: self.name,
            exp: self.exp as u32,
            char_class: self.class.parse().unwrap(),
            section_id: self.section_id.parse().unwrap(),
            appearance: CharacterAppearance {
                costume: self.costume as u16,
                skin: self.skin as u16,
                face: self.face as u16,
                head: self.head as u16,
                hair: self.hair as u16,
                hair_r: self.hair_r as u16,
                hair_g: self.hair_g as u16,
                hair_b: self.hair_b as u16,
                prop_x: self.prop_x,
                prop_y: self.prop_y,
            },
            techs: CharacterTechniques {
                techs: self.techs.iter().enumerate().take(19).map(|(i, t)| (tech::Technique::from_value(i as u8), TechLevel(*t)) ).collect()
            },
            config: CharacterConfig {
                raw_data: vec_to_array(self.config)
            },
            info_board: CharacterInfoboard {
                board: libpso::utf8_to_utf16_array!(self.infoboard, 172),
            },
            guildcard: CharacterGuildCard {
                description: self.guildcard,
            },
            option_flags: self.option_flags as u32,
            materials: CharacterMaterials {
                power: self.power as u32,
                mind: self.mind as u32,
                def: self.def as u32,
                evade: self.evade as u32,
                luck: self.luck as u32,
                hp: self.hp as u32,
                tp: self.tp as u32,
            },
            tech_menu: CharacterTechMenu {
                tech_menu: vec_to_array(self.tech_menu)
            },
            meseta: self.meseta as u32,
            bank_meseta: self.bank_meseta as u32,
        }
    }
}

#[derive(Debug, sqlx::FromRow)]
pub struct PgGuildCard {
}



#[derive(Debug, Serialize, Deserialize)]
pub struct PgWeapon {
    weapon: weapon::WeaponType,
    special: Option<weapon::WeaponSpecial>,
    grind: u8,
    attrs: HashMap<weapon::Attribute, i8>,
    tekked: bool,
}

impl From<weapon::Weapon> for PgWeapon {
    fn from(other: weapon::Weapon) -> PgWeapon {
        PgWeapon {
            weapon: other.weapon,
            special: other.special,
            grind: other.grind,
            attrs: other.attrs.iter().flatten().map(|attr| (attr.attr, attr.value)).collect(),
            tekked: other.tekked,
        }
    }
}

impl Into<weapon::Weapon> for PgWeapon {
    fn into(self) -> weapon::Weapon {
        let mut attrs: [Option<weapon::WeaponAttribute>; 3] = [None; 3];
        for (attr, (atype, value)) in attrs.iter_mut().zip(self.attrs.iter()) {
            *attr = Some(weapon::WeaponAttribute {
                attr: *atype,
                value: *value
            });
        }
        
        weapon::Weapon {
            weapon: self.weapon,
            special: self.special,
            grind: self.grind,
            attrs: attrs,
            tekked: self.tekked,
            modifiers: Vec::new(),
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PgArmor {
    armor: armor::ArmorType,
    dfp: u8,
    evp: u8,
    slots: u8,
}

impl From<armor::Armor> for PgArmor {
    fn from(other: armor::Armor) -> PgArmor {
        PgArmor {
            armor: other.armor,
            dfp: other.dfp,
            evp: other.evp,
            slots: other.slots,
        }
    }
}

impl Into<armor::Armor> for PgArmor {
    fn into(self) -> armor::Armor {
        armor::Armor {
            armor: self.armor,
            dfp: self.dfp,
            evp: self.evp,
            slots: self.slots,
            modifiers: Vec::new(),
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PgShield {
    shield: shield::ShieldType,
    dfp: u8,
    evp: u8,
}

impl From<shield::Shield> for PgShield {
    fn from(other: shield::Shield) -> PgShield {
        PgShield {
            shield: other.shield,
            dfp: other.dfp,
            evp: other.evp,
        }
    }
}

impl Into<shield::Shield> for PgShield {
    fn into(self) -> shield::Shield {
        shield::Shield {
            shield: self.shield,
            dfp: self.dfp,
            evp: self.evp,
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PgUnit {
    unit: unit::UnitType,
    modifier: Option<unit::UnitModifier>,
    armor_slot: u8,
}

impl From<unit::Unit> for PgUnit {
    fn from(other: unit::Unit) -> PgUnit {
        PgUnit {
            unit: other.unit,
            modifier: other.modifier,
            armor_slot: other.armor_slot,
        }
    }
}

impl Into<unit::Unit> for PgUnit {
    fn into(self) -> unit::Unit {
        unit::Unit {
            unit: self.unit,
            modifier: self.modifier,
            armor_slot: self.armor_slot,
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PgTool {
    pub tool: tool::ToolType,
}

impl From<tool::Tool> for PgTool {
    fn from(other: tool::Tool) -> PgTool {
        PgTool {
            tool: other.tool,
        }
    }
}

impl Into<tool::Tool> for PgTool {
    fn into(self) -> tool::Tool {
        tool::Tool {
            tool: self.tool,
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PgTechDisk {
    tech: tech::Technique,
    level: u32,
}

impl From<tech::TechniqueDisk> for PgTechDisk {
    fn from(other: tech::TechniqueDisk) -> PgTechDisk {
        PgTechDisk {
            tech: other.tech,
            level: other.level,
        }
    }
}

impl Into<tech::TechniqueDisk> for PgTechDisk {
    fn into(self) -> tech::TechniqueDisk {
        tech::TechniqueDisk {
            tech: self.tech,
            level: self.level
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PgMag {
    mag: mag::MagType,
    synchro: u8,
    color: u8,
}

impl From<mag::Mag> for PgMag {
    fn from(other: mag::Mag) -> PgMag {
        PgMag {
            mag: other.mag,
            synchro: other.synchro,
            color: other.color,
        }
    }
}

impl Into<mag::Mag> for PgMag {
    fn into(self) -> mag::Mag {
        /*mag::Mag {
            mag: self.mag,
            synchro: self.synchro,
            color: self.color,
            def: 500,
            pow: 0,
            dex: 0,
            mnd: 0,
            iq: 0,
            photon_blast: [None; 3],
            class: CharacterClass::HUmar,
            id: SectionID::Viridia,
    }*/
        let mut mag = mag::Mag::baby_mag(self.color as u16);
        mag.mag = self.mag;
        mag.synchro = self.synchro;
        mag
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PgESWeapon {
    esweapon: esweapon::ESWeaponType,
    special: Option<esweapon::ESWeaponSpecial>,
    name: String,
    grind: u8,
}

impl From<esweapon::ESWeapon> for PgESWeapon {
    fn from(other: esweapon::ESWeapon) -> PgESWeapon {
        PgESWeapon {
            esweapon: other.esweapon,
            special: other.special,
            name: other.name,
            grind: other.grind,
        }
    }
}

impl Into<esweapon::ESWeapon> for PgESWeapon {
    fn into(self) -> esweapon::ESWeapon {
        esweapon::ESWeapon {
            esweapon: self.esweapon,
            special: self.special,
            name: self.name,
            grind: self.grind,
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub enum PgItemDetail {
    Weapon(PgWeapon),
    Armor(PgArmor),
    Shield(PgShield),
    Unit(PgUnit),
    Tool(PgTool),
    TechDisk(PgTechDisk),
    Mag(PgMag),
    ESWeapon(PgESWeapon),
}

impl From<ItemDetail> for PgItemDetail {
    fn from(other: ItemDetail) -> PgItemDetail {
        match other {
            ItemDetail::Weapon(weapon) => PgItemDetail::Weapon(weapon.into()),
            ItemDetail::Armor(armor) => PgItemDetail::Armor(armor.into()),
            ItemDetail::Shield(shield) => PgItemDetail::Shield(shield.into()),
            ItemDetail::Unit(unit) => PgItemDetail::Unit(unit.into()),
            ItemDetail::Tool(tool) => PgItemDetail::Tool(tool.into()),
            ItemDetail::TechniqueDisk(tech_disk) => PgItemDetail::TechDisk(tech_disk.into()),
            ItemDetail::Mag(mag) => PgItemDetail::Mag(mag.into()),
            ItemDetail::ESWeapon(esweapon) => PgItemDetail::ESWeapon(esweapon.into()),
        }
    }
}

impl Into<ItemDetail> for PgItemDetail {
    fn into(self) -> ItemDetail {
        match self {
            PgItemDetail::Weapon(weapon) => ItemDetail::Weapon(weapon.into()),
            PgItemDetail::Armor(armor) => ItemDetail::Armor(armor.into()),
            PgItemDetail::Shield(shield) => ItemDetail::Shield(shield.into()),
            PgItemDetail::Unit(unit) => ItemDetail::Unit(unit.into()),
            PgItemDetail::Tool(tool) => ItemDetail::Tool(tool.into()),
            PgItemDetail::TechDisk(tech_disk) => ItemDetail::TechniqueDisk(tech_disk.into()),
            PgItemDetail::Mag(mag) => ItemDetail::Mag(mag.into()),
            PgItemDetail::ESWeapon(esweapon) => ItemDetail::ESWeapon(esweapon.into()),
        }
    }
}

#[derive(Debug, sqlx::FromRow)]
pub struct PgItem {
    pub id: i32,
    pub item: sqlx::types::Json<PgItemDetail>,
}


#[derive(Debug, Serialize, Deserialize)]
pub enum PgItemLocationDetail {
    Inventory {
        character_id: u32,
        #[serde(skip_serializing)]
        slot: usize,
        equipped: bool,
    },
    Bank {
        character_id: u32,
        name: String,
    },
    LocalFloor {
        character_id: u32,
        map_area: MapArea,
        x: f32,
        y: f32,
        z: f32,
    },
    SharedFloor {
        map_area: MapArea,
        x: f32,
        y: f32,
        z: f32,
    },
    Consumed,
    FedToMag {
        mag: u32,
    },
    Shop,
}

impl From<ItemLocation> for PgItemLocationDetail {
    fn from(other: ItemLocation) -> PgItemLocationDetail {
        match other {
            ItemLocation::Inventory{character_id, slot, equipped} => PgItemLocationDetail::Inventory{character_id: character_id.0, slot, equipped},
            ItemLocation::Bank{character_id, name} => PgItemLocationDetail::Bank{character_id: character_id.0, name: name.0},
            ItemLocation::LocalFloor{character_id, map_area, x,y,z} => PgItemLocationDetail::LocalFloor{character_id: character_id.0, map_area, x,y,z},
            ItemLocation::SharedFloor{map_area, x,y,z} => PgItemLocationDetail::SharedFloor{map_area, x,y,z},
            ItemLocation::Consumed => PgItemLocationDetail::Consumed,
            ItemLocation::FedToMag{mag} => PgItemLocationDetail::FedToMag{mag: mag.0},
            ItemLocation::Shop => PgItemLocationDetail::Shop,
        }
    }
}

impl Into<ItemLocation> for PgItemLocationDetail {
    fn into(self) -> ItemLocation {
        match self {
            PgItemLocationDetail::Inventory{character_id, slot, equipped} => ItemLocation::Inventory{character_id: CharacterEntityId(character_id), slot, equipped},
            PgItemLocationDetail::Bank{character_id, name} => ItemLocation::Bank{character_id: CharacterEntityId(character_id), name: BankName(name)},
            PgItemLocationDetail::LocalFloor{character_id, map_area, x,y,z} => ItemLocation::LocalFloor{character_id: CharacterEntityId(character_id), map_area, x,y,z},
            PgItemLocationDetail::SharedFloor{map_area, x,y,z} => ItemLocation::SharedFloor{map_area, x,y,z},
            PgItemLocationDetail::Consumed => ItemLocation::Consumed,
            PgItemLocationDetail::FedToMag{mag} => ItemLocation::FedToMag{mag: ItemEntityId(mag)},
            PgItemLocationDetail::Shop => ItemLocation::Shop,
        }
    }
}


#[derive(Debug, sqlx::FromRow)]
pub struct PgItemLocation {
    //pub id: i32,
    pub location: sqlx::types::Json<PgItemLocationDetail>,
    created_at: chrono::DateTime<chrono::Utc>,
}


#[derive(Debug, Serialize, Deserialize)]
pub enum PgMagModifierDetail {
    FeedMag(i32),
    BankMag,
    MagCell(i32),
    OwnerChange(CharacterClass, SectionID)
}

impl From<mag::MagModifier> for PgMagModifierDetail {
    fn from(other: mag::MagModifier) -> PgMagModifierDetail {
        match other {
            mag::MagModifier::FeedMag{food} => PgMagModifierDetail::FeedMag(food.0 as i32),
            mag::MagModifier::BankMag => PgMagModifierDetail::BankMag,
            mag::MagModifier::MagCell(cell) => PgMagModifierDetail::MagCell(cell.0 as i32),
            mag::MagModifier::OwnerChange(class, section_id) => PgMagModifierDetail::OwnerChange(class, section_id),
        }
    }
}

impl Into<mag::MagModifier> for PgMagModifierDetail {
    fn into(self) -> mag::MagModifier {
        match self {
            PgMagModifierDetail::FeedMag(food) => mag::MagModifier::FeedMag{food: ItemEntityId(food as u32)},
            PgMagModifierDetail::BankMag => mag::MagModifier::BankMag,
            PgMagModifierDetail::MagCell(cell) => mag::MagModifier::MagCell(ItemEntityId(cell as u32)),
            PgMagModifierDetail::OwnerChange(class, section_id) => mag::MagModifier::OwnerChange(class, section_id),
        }
    }
}

#[derive(Debug, sqlx::FromRow)]
pub struct PgMagModifier {
    mag: i32,
    pub modifier: sqlx::types::Json<PgMagModifierDetail>,
    created_at: chrono::DateTime<chrono::Utc>,
}


#[derive(Debug, sqlx::FromRow)]
pub struct PgItemWithLocation {
    pub id: i32,
    pub item: sqlx::types::Json<PgItemDetail>,
    pub location: sqlx::types::Json<PgItemLocationDetail>,
}


#[derive(Debug, sqlx::FromRow)]
pub struct PgMagModifierWithParameters {
    pub mag: i32,
    pub modifier: sqlx::types::Json<PgMagModifierDetail>,
    pub feed: Option<sqlx::types::Json<PgTool>>,
    pub cell: Option<sqlx::types::Json<PgTool>>,
}