#!/usr/bin/env python
import os
import time
import ConfigParser

from ansible.module_utils.basic import *

from mist.client import MistClient

DOCUMENTATION = '''
---
module: mist
short_description: Provision, monitor and manage machines with the mist.io service
description:
  - Manage machines in all of your added backends
  - You can add/remove multiple backends from multiple providers through mist.io service.
  - I(mist_email) and I(mist_password) can be skipped if I(~/.mist) config file is present.
  - See mist.client documentation for config file U(http://mistclient.readthedocs.org/en/latest/cmd/cmd.html)
requirements:
  - mist.client
options:
  mist_email:
    description:
      - Email to login to the mist.io service
    required: false
  mist_password:
    description:
      - Password to login to the mist.io service
    required: false
  mist_uri:
    default: https://mist.io
    description:
      - Url of the mist.io service. By default https://mist.io. But if you have a custom installation of mist.io you can provide the url here
    required: false
  state:
    description:
      - If provided it will instruct the module to trigger machine actions, otherwise it will only list information
    choices: ["present", "absent"]
    required: false
  name:
    description:
      - The name you want the machine to have
    required: false
  backend:
    description:
      - Can be either the backend's id or name
    required: true
  image_id:
    description:
      - Id of the OS image you want to use to provision your machine
    required: false
  size_id:
    description:
      - Id of the machine size you want to use
    required: false
  location_id:
    description:
      - Id of the location/region you want to provision your machine to
    required: false
  key:
    description:
      - Name of the SSH-Key you want to associate with the machine. If I(None), the default SSH Key will be used
    required: false
  image_extra:
    description:
      - Needed only when provisioning to Linode Provider
    required: false
  image_extra:
    description:
      - Needed only when provisioning to Linode Provider
    required: false
  wait:
    description:
    - If I(True), the module will wait for the machine's SSH Daemon to be up and running and the SSH Key associated
    default: False
    required: false
  wait_time:
    description:
      - Time to wait when waiting for machine to be probed or monitor to be up and running
    default: 600
    required: false
  monitoring:
    description:
      - If I(True), it will enable monitor to the machine
    default: False
    required: false
  wait_for_stats:
    description:
      - When enabling monitoring for the first time, it may take some time for the collectd agent to be installed.
      - If I(True), it will wait for the monitoring stats to start
    default: False
    required: false
author: "Chris Loukas <commixon@gmail.com>"
version_added: "1.7.1"

'''

EXAMPLES = '''
- name: Provision Ubuntu machine to EC2
  mist:
    mist_email: your@email.com
    mist_password: yourpassword
    backend: EC2
    state: present
    name: MyMachine
    key: myKey
    image_id: ami-bddaa2bc
    size_id: m1.small
    location_id: 0

- name: Provision SUSE machine on EC2 and enable monitoring
  mist:
    mist_email: your@email.com
    mist_password: yourpassword
    backend: EC2
    state: present
    name: MyMachine
    key: myKey
    image_id: ami-9178e890
    size_id: m1.small
    location_id: 0
    monitoring: true
    wait_for_stats: true

- name: List info for machine with name dbServer
  mist:
    mist_email: your@email.com
    mist_password: yourpassword
    backend: EC2
    name: dbServer
  register: machine

'''


def authenticate(module):
    home_path = os.getenv("HOME")
    config_path = os.path.join(home_path, ".mist")
    config = ConfigParser.ConfigParser()

    mist_uri = module.params.get('mist_uri')
    mist_email = module.params.get('mist_email')
    mist_password = module.params.get('mist_password')


    # Set default mist uri
    config.add_section("mist.io")
    config.set("mist.io", "mist_uri", "https://mist.io")

    # Set default credentials
    config.add_section("mist.credentials")
    config.set("mist.credentials", "email", None)
    config.set("mist.credentials", "password", None)

    # Read configuration file
    if os.path.isfile(config_path):
            config.readfp(open(config_path))

    mist_uri = config.get("mist.io", "mist_uri")
    if not mist_email:
        mist_email = config.get("mist.credentials", "email") or ""

    if not mist_password:
        mist_password = config.get("mist.credentials", "password") or ""

    return init_client(mist_uri, mist_email, mist_password)


def init_client(mist_uri="https://mist.io", email=None, password=None):
    client = MistClient(mist_uri, email, password)
    return client


def determine_action(state):
    if not state:
        return "list"
    else:
        return "addremove"


def choose_backend(module, client):
    backend_name = module.params.get('backend')
    backend = client.search_backend(backend_name)

    if not backend:
        module.fail_json(msg="You have to provide a valid backend id or name")

    return backend


def list_machines(module, client):
    backend = choose_backend(module, client)
    machine_name = module.params.get('name')

    if not machine_name:
        result = {}
        for key in backend.machines.keys():
            machine = backend.machines[key]
            result[key] = machine.info
    else:
        result = {}
        machine = backend.search_machine(machine_name)
        if machine:
            result = machine.info

    if result:
        info = result
        network_interfaces = info['extra'].get('network_interfaces', None)
        if network_interfaces:
            info['extra'].pop('network_interfaces')
    else:
        info = result


    module.exit_json(changed=False, info=info)


def machine_action(module, client):
    backend = choose_backend(module, client)
    machine_name = module.params.get('name')
    if not machine_name:
        module.fail_json(msg="You have to provide a name for the machine")

    machine_state, machine = check_state(backend, machine_name)

    desired_state = module.params.get('state')

    if machine_state == "present" and desired_state == "present":
        change = False
        monitoring = module.params.get('monitoring')
        if monitoring:
            machine.enable_monitoring()
            change = True

        info = machine.info
        network_interfaces = info['extra'].get('network_interfaces', None)
        if network_interfaces:
            info['extra'].pop('network_interfaces')
        module.exit_json(changed=change, info=info)
    elif machine_state == "present" and desired_state == "absent":
        machine.destroy()
        module.exit_json(changed=True)
    elif machine_state == "absent" and desired_state == "absent":
        module.exit_json(changed=False)
    elif machine_state == "absent" and desired_state == "present":
        machine = create_machine(module, client, backend)

        info = machine.info
        network_interfaces = info['extra'].get('network_interfaces', None)
        if network_interfaces:
            info['extra'].pop('network_interfaces')

        module.exit_json(changed=True, info=info)


def check_state(backend, machine_name):
    state = "absent"
    machine = backend.search_machine(machine_name)
    if machine:
        state = "present"

    return state, machine


def create_machine(module, client, backend):
    key_name = module.params.get('key')
    key = client.keys[key_name]

    machine_name = module.params.get('name')
    image_id = module.params.get('image_id')
    size_id = module.params.get('size_id')
    location_id = module.params.get('location_id')
    image_extra = module.params.get('image_extra')
    disk = module.params.get('disk')

    backend.create_machine(machine_name, key, image_id, location_id, size_id, image_extra, disk)
    backend.update_machines()

    wait = module.params.get('wait')
    wait_time = module.params.get('wait_time')
    monitoring = module.params.get('monitoring')
    wait_for_stats = module.params.get('wait_for_stats')

    if backend.provider == "nephoscale":
        start_time = time.time()
        while time.time() < start_time + wait_time:
            time.sleep(15)
            machine = backend.search_machine(machine_name)
            if machine:
                break
            else:
                backend.update_machines()
    else:
        machine = backend.search_machine(machine_name)

    if wait or monitoring:
        start_time = time.time()
        while time.time() < start_time + wait_time:
            try:
                time.sleep(15)
                probe_info = machine.probe()
                uptime = probe_info.get('uptime', None)
                if uptime:
                    backend.update_machines()
                    machine = backend.search_machine(machine_name)
                    break
            except:
                backend.update_machines()
                machine = backend.search_machine(machine_name)

    if monitoring:
        machine.enable_monitoring()

    if monitoring and wait_for_stats:
        start_time = time.time()
        while time.time() < start_time + wait_time:
            try:
                time.sleep(15)
                stats = machine.get_stats()
                if stats:
                    break
            except:
                backend.update_machines()
                machine = backend.search_machine(machine_name)

    return machine


def main():
    module = AnsibleModule(
        argument_spec=dict(
            mist_uri=dict(default='https://mist.io', type='str'),
            mist_email=dict(required=False, type='str'),
            mist_password=dict(required=False, type='str'),
            backend=dict(required=True, type='str'),
            state=dict(required=False, type='str', choices=['present', 'absent']),
            image_id=dict(required=False, type='str'),
            size_id=dict(required=False, type='str'),
            location_id=dict(required=False, type='str'),
            key=dict(required=False, type='str'),
            image_extra=dict(required=False, type='str'),
            disk=dict(required=False, type='str'),
            name=dict(required=False, type='str'),
            wait=dict(required=False, default=False, type='bool'),
            wait_time=dict(required=False, default=600, type='int'),
            monitoring=dict(required=False, type='bool', default=False),
            wait_for_stats=dict(required=False, type='bool', default=False)
        )
    )

    client = authenticate(module)

    #Determine which action to run (e.g. list machines, create machine etc)
    state = module.params.get('state')
    action = determine_action(state)

    if action == "list":
        list_machines(module, client)
    else:
        machine_action(module, client)



main()