use crate::utils::print_table;
use rusoto_autoscaling::{
    AutoScalingGroupNamesType, Autoscaling, AutoscalingClient, DescribeScalingActivitiesType,
    Instance,
};
use rusoto_core::Region;
use structopt::StructOpt;

#[derive(Debug, StructOpt)]
pub enum AutoScalingGroupOpt {
    #[structopt(about = "display basic info")]
    Info(SearchQueryOpt),
    #[structopt(visible_alias = "act", about = "display activities")]
    Activities(SearchQueryOpt),
    #[structopt(visible_alias = "inst", about = "display instances")]
    Instances(SearchQueryOpt),
}
#[derive(Debug, StructOpt)]
pub struct SearchQueryOpt {
    #[structopt(
        short = "q",
        long,
        help = "ambiguous search with asterisk on target group name or ALB arn.  if set comma, search OR"
    )]
    query: Option<String>,
}

pub async fn matcher(opt: AutoScalingGroupOpt) {
    match opt {
        AutoScalingGroupOpt::Info(opt) => info(opt).await,
        AutoScalingGroupOpt::Activities(opt) => activities(opt).await,
        AutoScalingGroupOpt::Instances(opt) => instances(opt).await,
    }
}
async fn info(opt: SearchQueryOpt) {
    let asg = get_auto_scaling_groups(&opt).await;
    let len = asg.len();
    let rows: Vec<Vec<String>> = asg
        .into_iter()
        .map(|t| {
            vec![
                t.name,
                t.instances.len().to_string(),
                t.desired_capacity.to_string(),
                t.min_capacity.to_string(),
                t.max_capacity.to_string(),
            ]
        })
        .collect();
    print_table(vec!["Name", "Instances", "Desired", "Min", "Max"], rows);
    println!("counts: {}", len);
}
async fn activities(opt: SearchQueryOpt) {
    let asg = get_auto_scaling_groups(&opt).await;
    if asg.len() != 1 {
        println!("need to be narrowed to 1");
        return;
    }
    let a = get_activities(asg.first().unwrap().name.clone()).await;
    let len = a.len();
    let rows: Vec<Vec<String>> = a
        .into_iter()
        .map(|t| vec![t.status, t.description, t.start_at, t.end_at])
        .collect();
    print_table(vec!["Status", "Desc", "StartTime", "EndTime"], rows);
    println!("counts: {}", len);
}

async fn instances(opt: SearchQueryOpt) {
    let asg = get_auto_scaling_groups(&opt).await;
    if asg.len() != 1 {
        println!("need to be narrowed to 1");
        return;
    }
    let inst: Vec<Instance> = asg.into_iter().flat_map(|a| a.instances).collect();
    let len = inst.len();
    let rows: Vec<Vec<String>> = inst
        .into_iter()
        .map(|i| {
            vec![
                i.instance_id,
                i.lifecycle_state,
                i.instance_type.unwrap_or_default(),
                i.availability_zone,
                i.health_status,
            ]
        })
        .collect();
    print_table(
        vec!["ID", "LifeCycle", "InstanceType", "AZ", "Status"],
        rows,
    );
    println!("counts: {}", len);
}

struct AutoScalingGroup {
    name: String,
    instances: Vec<Instance>,
    min_capacity: i64,
    max_capacity: i64,
    desired_capacity: i64,
    tags: Vec<Tag>,
}
struct Tag {
    key: String,
    value: Option<String>,
}
async fn get_auto_scaling_groups(opt: &SearchQueryOpt) -> Vec<AutoScalingGroup> {
    let elb = AutoscalingClient::new(Region::ApNortheast1);
    let mut m: Option<String> = None;
    let mut vector: Vec<AutoScalingGroup> = vec![];
    loop {
        let (mut v, mark) = auto_scaling_group(&elb, &m).await;
        m = mark;
        vector.append(&mut v);
        if m.is_none() {
            break;
        }
    }
    vector
        .into_iter()
        .filter(|t| search_name(&opt.query, &t.name, &t.tags))
        .collect()
}

async fn auto_scaling_group(
    cli: &AutoscalingClient,
    marker: &Option<String>,
) -> (Vec<AutoScalingGroup>, Option<String>) {
    match cli
        .describe_auto_scaling_groups(AutoScalingGroupNamesType {
            auto_scaling_group_names: None,
            max_records: None,
            next_token: marker.clone(),
        })
        .await
    {
        Ok(res) => {
            let asg = res.auto_scaling_groups;
            (
                asg.into_iter()
                    .map(|t| AutoScalingGroup {
                        name: t.auto_scaling_group_name,
                        instances: t.instances.unwrap_or_default(),
                        min_capacity: t.min_size,
                        max_capacity: t.max_size,
                        desired_capacity: t.desired_capacity,
                        tags: t
                            .tags
                            .map(|ot| {
                                ot.into_iter()
                                    .map(|t| Tag {
                                        key: t.key.unwrap_or_default(),
                                        value: t.value,
                                    })
                                    .collect()
                            })
                            .unwrap_or_default(),
                    })
                    .collect(),
                res.next_token,
            )
        }
        Err(err) => panic!(err.to_string()),
    }
}

fn search_name(query: &Option<String>, name: &str, tags: &[Tag]) -> bool {
    if query.is_none() || name.is_empty() {
        return true;
    }
    let tg: String = name.to_string();
    let qu: String = query.as_ref().unwrap().clone();
    for q in qu.split(',') {
        if tg.contains(q) {
            return true;
        }
        if tags
            .iter()
            .any(|t| t.key.contains(q) || t.value.as_ref().filter(|v| v.contains(q)).is_some())
        {
            return true;
        }
    }

    false
}
#[test]
fn test_search_name() {
    assert_eq!(
        search_name(&Some("api".to_string()), &"aa".to_string(), &vec![]),
        false
    );

    assert_eq!(
        search_name(
            &Some("api,test".to_string()),
            &"test-api".to_string(),
            &vec![]
        ),
        true
    );
    assert_eq!(
        search_name(&Some("api".to_string()), &"ap".to_string(), &vec![]),
        false
    );
    assert_eq!(
        search_name(
            &Some("test".to_string()),
            &"ap".to_string(),
            &vec![Tag {
                key: "test".to_string(),
                value: None
            }]
        ),
        true
    );
    assert_eq!(
        search_name(
            &Some("test".to_string()),
            &"ap".to_string(),
            &vec![Tag {
                key: "tag".to_string(),
                value: Some("test".to_string())
            }]
        ),
        true
    );
}

struct Activity {
    status: String,
    description: String,
    start_at: String,
    end_at: String,
}
async fn get_activities(asg_name: String) -> Vec<Activity> {
    let cli = AutoscalingClient::new(Region::ApNortheast1);
    match cli
        .describe_scaling_activities(DescribeScalingActivitiesType {
            activity_ids: None,
            auto_scaling_group_name: Some(asg_name),
            max_records: None,
            next_token: None,
        })
        .await
    {
        Ok(res) => res
            .activities
            .into_iter()
            .map(|a| Activity {
                status: a.status_code,
                description: a.description.unwrap_or_default(),
                start_at: a.start_time,
                end_at: a.end_time.unwrap_or_default(),
            })
            .collect(),
        Err(err) => panic!(err.to_string()),
    }
}
