module Terraforming
  class CLI < Thor
    class_option :merge, type: :string, desc: "tfstate file to merge"
    class_option :overwrite, type: :boolean, desc: "Overwrite existing tfstate"
    class_option :tfstate, type: :boolean, desc: "Generate tfstate"
    class_option :profile, type: :string, desc: "AWS credentials profile"
    class_option :region, type: :string, desc: "AWS region"
    class_option :assume, type: :string, desc: "Role ARN to assume"
    class_option :use_bundled_cert,
                 type: :boolean,
                 desc: "Use the bundled CA certificate from AWS SDK"

    desc "alb", "ALB"
    def alb
      execute(Terraforming::Resource::ALB, options)
    end

    desc "asg", "AutoScaling Group"
    def asg
      execute(Terraforming::Resource::AutoScalingGroup, options)
    end

    desc "cwa", "CloudWatch Alarm"
    def cwa
      execute(Terraforming::Resource::CloudWatchAlarm, options)
    end

    desc "dbpg", "Database Parameter Group"
    def dbpg
      execute(Terraforming::Resource::DBParameterGroup, options)
    end

    desc "dbsg", "Database Security Group"
    def dbsg
      execute(Terraforming::Resource::DBSecurityGroup, options)
    end

    desc "dbsn", "Database Subnet Group"
    def dbsn
      execute(Terraforming::Resource::DBSubnetGroup, options)
    end

    desc "ddb", "DynamoDB"
    def ddb
      execute(Terraforming::Resource::DynamoDB, options)
    end

    desc "ec2", "EC2"
    def ec2
      execute(Terraforming::Resource::EC2, options)
    end

    desc "ecc", "ElastiCache Cluster"
    def ecc
      execute(Terraforming::Resource::ElastiCacheCluster, options)
    end

    desc "ecsn", "ElastiCache Subnet Group"
    def ecsn
      execute(Terraforming::Resource::ElastiCacheSubnetGroup, options)
    end

    desc "eip", "EIP"
    def eip
      execute(Terraforming::Resource::EIP, options)
    end

    desc "efs", "EFS File System"
    def efs
      execute(Terraforming::Resource::EFSFileSystem, options)
    end

    desc "elb", "ELB"
    def elb
      execute(Terraforming::Resource::ELB, options)
    end

    desc "iamg", "IAM Group"
    def iamg
      execute(Terraforming::Resource::IAMGroup, options)
    end

    desc "iamgm", "IAM Group Membership"
    def iamgm
      execute(Terraforming::Resource::IAMGroupMembership, options)
    end

    desc "iamgp", "IAM Group Policy"
    def iamgp
      execute(Terraforming::Resource::IAMGroupPolicy, options)
    end

    desc "iamip", "IAM Instance Profile"
    def iamip
      execute(Terraforming::Resource::IAMInstanceProfile, options)
    end

    desc "iamp", "IAM Policy"
    def iamp
      execute(Terraforming::Resource::IAMPolicy, options)
    end

    desc "iampa", "IAM Policy Attachment"
    def iampa
      execute(Terraforming::Resource::IAMPolicyAttachment, options)
    end

    desc "iamr", "IAM Role"
    def iamr
      execute(Terraforming::Resource::IAMRole, options)
    end

    desc "iamrp", "IAM Role Policy"
    def iamrp
      execute(Terraforming::Resource::IAMRolePolicy, options)
    end

    desc "iamu", "IAM User"
    def iamu
      execute(Terraforming::Resource::IAMUser, options)
    end

    desc "iamup", "IAM User Policy"
    def iamup
      execute(Terraforming::Resource::IAMUserPolicy, options)
    end

    desc "kmsa", "KMS Key Alias"
    def kmsa
      execute(Terraforming::Resource::KMSAlias, options)
    end

    desc "kmsk", "KMS Key"
    def kmsk
      execute(Terraforming::Resource::KMSKey, options)
    end

    desc "lc", "Launch Configuration"
    def lc
      execute(Terraforming::Resource::LaunchConfiguration, options)
    end

    desc "igw", "Internet Gateway"
    def igw
      execute(Terraforming::Resource::InternetGateway, options)
    end

    desc "nacl", "Network ACL"
    def nacl
      execute(Terraforming::Resource::NetworkACL, options)
    end

    desc "nat", "NAT Gateway"
    def nat
      execute(Terraforming::Resource::NATGateway, options)
    end

    desc "nif", "Network Interface"
    def nif
      execute(Terraforming::Resource::NetworkInterface, options)
    end

    desc "r53r", "Route53 Record"
    def r53r
      execute(Terraforming::Resource::Route53Record, options)
    end

    desc "r53z", "Route53 Hosted Zone"
    def r53z
      execute(Terraforming::Resource::Route53Zone, options)
    end

    desc "rds", "RDS"
    def rds
      execute(Terraforming::Resource::RDS, options)
    end

    desc "rs", "Redshift"
    def rs
      execute(Terraforming::Resource::Redshift, options)
    end

    desc "rt", "Route Table"
    def rt
      execute(Terraforming::Resource::RouteTable, options)
    end

    desc "rta", "Route Table Association"
    def rta
      execute(Terraforming::Resource::RouteTableAssociation, options)
    end

    desc "s3", "S3"
    def s3
      execute(Terraforming::Resource::S3, options)
    end

    desc "sg", "Security Group"
    def sg
      execute(Terraforming::Resource::SecurityGroup, options)
    end

    desc "sn", "Subnet"
    def sn
      execute(Terraforming::Resource::Subnet, options)
    end

    desc "sqs", "SQS"
    def sqs
      execute(Terraforming::Resource::SQS, options)
    end

    desc "vpc", "VPC"
    def vpc
      execute(Terraforming::Resource::VPC, options)
    end

    desc "vgw", "VPN Gateway"
    def vgw
      execute(Terraforming::Resource::VPNGateway, options)
    end

    desc "snst", "SNS Topic"
    def snst
      execute(Terraforming::Resource::SNSTopic, options)
    end

    desc "snss", "SNS Subscription"
    def snss
      execute(Terraforming::Resource::SNSTopicSubscription, options)
    end

    private

    def configure_aws(options)
      Aws.config[:credentials] = Aws::SharedCredentials.new(profile_name: options[:profile]) if options[:profile]
      Aws.config[:region] = options[:region] if options[:region]

      if options[:assume]
        args = { role_arn: options[:assume], role_session_name: "terraforming-session-#{Time.now.to_i}" }
        args[:client] = Aws::STS::Client.new(profile: options[:profile]) if options[:profile]
        Aws.config[:credentials] = Aws::AssumeRoleCredentials.new(args)
      end

      Aws.use_bundled_cert! if options[:use_bundled_cert]
    end

    def execute(klass, options)
      configure_aws(options)
      result = options[:tfstate] ? tfstate(klass, options[:merge]) : tf(klass)

      if options[:tfstate] && options[:merge] && options[:overwrite]
        open(options[:merge], "w+") do |f|
          f.write(result)
          f.flush
        end
      else
        puts result
      end
    end

    def tf(klass)
      klass.tf
    end

    def tfstate(klass, tfstate_path)
      tfstate = tfstate_path ? MultiJson.load(open(tfstate_path).read) : tfstate_skeleton
      tfstate["serial"] = tfstate["serial"] + 1
      tfstate["modules"][0]["resources"] = tfstate["modules"][0]["resources"].merge(klass.tfstate)
      MultiJson.encode(tfstate, pretty: true)
    end

    def tfstate_skeleton
      {
        "version" => 1,
        "serial" => 0,
        "modules" => [
          {
            "path" => [
              "root"
            ],
            "outputs" => {},
            "resources" => {},
          }
        ]
      }
    end
  end
end
