#!python

import glob
import json

import argparse

from beneath import config
from beneath.client import BeneathError, Client


def main():
  parser = create_argument_parser()
  args = parser.parse_args()
  try:
    func = args.func
  except AttributeError:
    parser.error("too few arguments")
  func(args)


def create_argument_parser():
  parser = argparse.ArgumentParser()
  _root = parser.add_subparsers()

  # auth parser
  _auth = _root.add_parser('auth')
  _auth.set_defaults(func=auth)
  _auth.add_argument('secret', type=str)

  # organization parser and subparsers
  _organization = _root.add_parser('organization').add_subparsers()

  _organization_create = _organization.add_parser('create')
  _organization_create.set_defaults(func=organization_create)
  _organization_create.add_argument('name', type=str)

  _organization_info = _organization.add_parser('info')
  _organization_info.set_defaults(func=organization_info)
  _organization_info.add_argument('--name', type=str, required=True)

  # project parser and subparsers
  _project = _root.add_parser('project').add_subparsers()

  _project_list = _project.add_parser('list')
  _project_list.set_defaults(func=project_list)

  _project_create = _project.add_parser('create')
  _project_create.set_defaults(func=project_create)
  _project_create.add_argument('name', type=str)
  _project_create.add_argument('--display-name', type=str)
  _project_create.add_argument('--organization-id', type=str, required=True)
  _project_create.add_argument('--description', type=str)
  _project_create.add_argument('--site-url', type=str)
  _project_create.add_argument('--photo-url', type=str)

  _project_update = _project.add_parser('update')
  _project_update.set_defaults(func=project_update)
  _project_update.add_argument('name', type=str)
  _project_update.add_argument('--display-name', type=str)
  _project_update.add_argument('--description', type=str)
  _project_update.add_argument('--site-url', type=str)
  _project_update.add_argument('--photo-url', type=str)

  _project_delete = _project.add_parser('delete')
  _project_delete.set_defaults(func=project_delete)
  _project_delete.add_argument('name', type=str)

  # service parser and subparsers
  _service = _root.add_parser('service').add_subparsers()

  _service_list = _service.add_parser('list')
  _service_list.set_defaults(func=service_list)
  _service_list.add_argument('--organization-name', type=str, required=True)

  _service_create = _service.add_parser('create')
  _service_create.set_defaults(func=service_create)
  _service_create.add_argument('name', type=str)
  _service_create.add_argument('--organization-id', type=str, required=True)
  _service_create.add_argument('--read-bytes-quota', type=int, required=True)
  _service_create.add_argument('--write-bytes-quota', type=int, required=True)

  _service_update = _service.add_parser('update')
  _service_update.set_defaults(func=service_update)
  _service_update.add_argument('service_id', type=str)
  _service_update.add_argument('--name', type=str)
  _service_update.add_argument('--organization-id', type=str)
  _service_update.add_argument('--read-bytes-quota', type=int)
  _service_update.add_argument('--write-bytes-quota', type=int)

  _service_delete = _service.add_parser('delete')
  _service_delete.set_defaults(func=service_delete)
  _service_delete.add_argument('service_id', type=str)

  _service_add_perm = _service.add_parser('update-permissions')
  _service_add_perm.set_defaults(func=service_permissions)
  _service_add_perm.add_argument('service_id', type=str)
  _service_add_perm.add_argument('stream_id', type=str)
  _service_add_perm.add_argument('--read', type=str2bool, required=True)
  _service_add_perm.add_argument('--write', type=str2bool, required=True)

  _service_issue_secret = _service.add_parser('issue-secret')
  _service_issue_secret.set_defaults(func=service_issue_secret)
  _service_issue_secret.add_argument('service_id', type=str)
  _service_issue_secret.add_argument('--description', type=str, required=True)

  _service_list_secrets = _service.add_parser('list-secrets')
  _service_list_secrets.set_defaults(func=service_list_secrets)
  _service_list_secrets.add_argument('service_id', type=str)

  _service_revoke_secret = _service.add_parser('revoke-secret')
  _service_revoke_secret.set_defaults(func=revoke_secret)
  _service_revoke_secret.add_argument('secret_id', type=str)

  # model parser and subparsers
  _model = _root.add_parser('model').add_subparsers()

  _model_init = _model.add_parser('init')
  _model_init.set_defaults(func=model_init)
  _model_init.add_argument('model', type=str)
  _model_init.add_argument('--project', type=str, required=True)

  _model_sim = _model.add_parser('simulate')
  _model_sim.set_defaults(func=model_sim)
  _model_sim.add_argument('--sample-size', type=int)

  _model_stage = _model.add_parser('stage')
  _model_stage.set_defaults(func=model_stage)
  _model_stage.add_argument('--update', type=str2bool, nargs='?', const=True, default=False)

  _model_delete = _model.add_parser('delete')
  _model_delete.set_defaults(func=model_delete)
  _model_delete.add_argument('--confirm', type=str2bool, required=True, nargs='?', const=True, default=False)

  _model_deploy = _model.add_parser('deploy')
  _model_deploy.set_defaults(func=model_deploy)
  _model_deploy.add_argument('--update', type=str)

  # stream parser and subparsers
  _stream = _root.add_parser('stream').add_subparsers()

  _stream_list = _stream.add_parser('list')
  _stream_list.set_defaults(func=stream_list)
  _stream_list.add_argument('-p', '--project', type=str, required=True)

  # root-stream parser and subparsers
  _stream_root = _root.add_parser('root-stream').add_subparsers()

  _stream_create_ext = _stream_root.add_parser('create')
  _stream_create_ext.set_defaults(func=stream_create_ext)
  _stream_create_ext.add_argument('-f', '--file', type=str, required=True, help="this file should contain the GraphQL schema for the stream you would like to create")
  _stream_create_ext.add_argument('-p', '--project', type=str, required=True)
  _stream_create_ext.add_argument('--manual', type=str2bool, nargs='?', const=True, default=False)
  _stream_create_ext.add_argument('--batch', type=str2bool, nargs='?', const=True, default=False)

  _stream_update_ext = _stream_root.add_parser('update')
  _stream_update_ext.set_defaults(func=stream_update_ext)
  _stream_update_ext.add_argument('stream', type=str)
  _stream_update_ext.add_argument('-f', '--file', type=str, required=True, help="This file should contain the stream's schema and an updated description. Only the description should change, the schema itself should not change.")
  _stream_update_ext.add_argument('-p', '--project', type=str, required=True)
  _stream_update_ext.add_argument('--manual', type=str2bool, nargs='?', const=True, default=False)

  _stream_delete_ext = _stream_root.add_parser('delete')
  _stream_delete_ext.set_defaults(func=stream_delete_ext)
  _stream_delete_ext.add_argument('stream', type=str)
  _stream_delete_ext.add_argument('-p', '--project', type=str, required=True)

  # root-stream batch parser and subparsers
  _stream_root_batch = _stream_root.add_parser('batch').add_subparsers()

  _stream_ext_batch_create = _stream_root_batch.add_parser('create')
  _stream_ext_batch_create.set_defaults(func=stream_ext_batch_create)
  _stream_ext_batch_create.add_argument('stream', type=str)
  _stream_ext_batch_create.add_argument('-p', '--project', type=str, required=True)

  _stream_ext_batch_commit = _stream_root_batch.add_parser('commit')
  _stream_ext_batch_commit.set_defaults(func=stream_ext_batch_commit)
  _stream_ext_batch_commit.add_argument('instance', type=str)

  _stream_ext_batches_clear = _stream_root_batch.add_parser('clear')
  _stream_ext_batches_clear.set_defaults(func=stream_ext_batches_clear)
  _stream_ext_batches_clear.add_argument('stream', type=str)
  _stream_ext_batches_clear.add_argument('-p', '--project', type=str, required=True)

  return parser


def get_client():
  return Client()


def pretty_print_graphql_result(result, fields=None):
  if fields:
    result = {k: v for k, v in result.items() if k in fields}
  pretty = json.dumps(result, indent=True)
  print(pretty)


def str2bool(v):
  if isinstance(v, bool):
    return v
  if v.lower() in ('yes', 'true', 't', 'y', '1'):
    return True
  if v.lower() in ('no', 'false', 'f', 'n', '0'):
    return False
  raise argparse.ArgumentTypeError('Boolean value expected.')


def auth(args):
  try:
    Client(secret=args.secret)
    config.write_secret(args.secret)
    print("You have authenticated successfully!")
  except BeneathError:
    config.write_secret("")
    print("Your attempt to authenticate failed. Are you using an API secret generated in the Beneath web app?")


def organization_create(args):
  client = get_client()
  result = client.create_organization(name=args.name)
  pretty_print_graphql_result(result)


def organization_info(args):
  client = get_client()
  result = client.get_organization_by_name(name=args.name)
  pretty_print_graphql_result(result)


def project_list(args):
  # start client with the secret stored in .beneath/secret
  client = get_client()

  # submit GraphQL query to get the user's ID
  result = client.get_me()
  user_id = result['userID']

  # submit GraphQL query to get the user's projects
  result = client.get_user_by_id(user_id)
  projects = result['projects']

  # print out the projects to the user
  for project in projects:
    print(project['name'])


def project_create(args):
  client = get_client()
  result = client.create_project(
    name=args.name,
    display_name=args.display_name,
    organization_id=args.organization_id,
    description=args.description,
    site_url=args.site_url,
    photo_url=args.photo_url,
  )
  pretty_print_graphql_result(result)


def project_update(args):
  client = get_client()
  project = client.get_project_by_name(args.name)
  result = client.update_project(
    project_id=project['projectID'],
    display_name=args.display_name,
    description=args.description,
    site_url=args.site_url,
    photo_url=args.photo_url,
  )
  pretty_print_graphql_result(result)


def project_delete(args):
  client = get_client()
  project = client.get_project_by_name(args.name)
  result = client.delete_project(project_id=project['projectID'])
  pretty_print_graphql_result(result)


def service_list(args):
  client = get_client()
  result = client.get_organization_by_name(name=args.organization_name)
  services = result['services']
  for service in services:
    pretty_print_graphql_result(service)


def service_create(args):
  client = get_client()
  result = client.create_service(
    name=args.name,
    organization_id=args.organization_id,
    read_bytes_quota=args.read_bytes_quota,
    write_bytes_quota=args.write_bytes_quota,
  )
  pretty_print_graphql_result(result)


def service_update(args):
  client = get_client()
  result = client.update_service(
    service_id=args.service_id,
    name=args.name,
    organization_id=args.organization_id,
    read_bytes_quota=args.read_bytes_quota,
    write_bytes_quota=args.write_bytes_quota,
  )
  pretty_print_graphql_result(result)


def service_delete(args):
  client = get_client()
  result = client.delete_service(
    service_id=args.service_id,
  )
  pretty_print_graphql_result(result)


def service_permissions(args):
  client = get_client()
  result = client.update_service_permissions(
    service_id=args.service_id,
    stream_id=args.stream_id,
    read=args.read,
    write=args.write,
  )
  pretty_print_graphql_result(result)


def service_issue_secret(args):
  client = get_client()
  result = client.issue_service_secret(
    service_id=args.service_id,
    description=args.description,
  )
  print("\n" + "Keep your secret safe. You won't be shown it again." + "\n")
  pretty_print_graphql_result(result)


def service_list_secrets(args):
  client = get_client()
  result = client.list_service_secrets(service_id=args.service_id)
  pretty_print_graphql_result(result)


def revoke_secret(args):
  client = get_client()
  result = client.revoke_secret(
    secret_id=args.secret_id,
  )
  pretty_print_graphql_result(result)


def stream_list(args):
  # start client with the secret stored in .beneath/secret
  client = get_client()

  # submit GraphQL query to get a project's streams
  result = client.get_project_by_name(args.project)
  streams = result['streams']

  # print out the streams to the user
  for streamname in streams:
    print(streamname['name'])

  if len(streams) == 0:
    print("There are no streams currently in this project")


def model_init(args):
  # TODO
  raise Exception("Not implemented")


def model_sim(args):
  # TODO
  raise Exception("Not implemented")


def model_stage(args):
  # read config
  with open("config.json", "r") as f:
    conf = json.load(f)

  # read schemas
  def _read_schema(path):
    with open(path, "r") as f:
      return f.read()
  schemas = [_read_schema(p) for p in glob.glob("schemas/*.graphql")]

  # get client
  client = get_client()

  # get project
  result = client.get_project_by_name(conf.get("project", None))
  project_id = result['projectID']
  project_name = result['name']

  # get input stream IDs
  input_stream_ids = []
  for dep in conf.get("dependencies", []):
    project, stream = dep.split("/")
    details = client.get_stream_details(project, stream)
    input_stream_ids.append(details["streamID"])

  # stage model
  name = conf.get("name", None)
  if args.update:
    model = client.get_model_details(project_name, name)
    result = client.update_model(
      model_id=model["modelID"],
      source_url=conf.get("source_url", None),
      description=conf.get("description", None),
      input_stream_ids=input_stream_ids,
      output_stream_schemas=schemas,
    )
  else:
    result = client.create_model(
      project_id=project_id,
      name=name,
      kind=conf.get("kind", None),
      source_url=conf.get("source_url", None),
      description=conf.get("description", None),
      input_stream_ids=input_stream_ids,
      output_stream_schemas=schemas,
    )

  # print out model details
  pretty_print_graphql_result(result)


def model_delete(args):
  with open("config.json", "r") as f:
    conf = json.load(f)

  client = get_client()
  model = client.get_model_details(
    project_name=conf.get("project", None),
    model_name=conf.get("name", None),
  )

  result = client.delete_model(model["modelID"])
  pretty_print_graphql_result(result)


def model_deploy(args):
  # TODO
  raise Exception("Not implemented")


def stream_create_ext(args):
  # start client with the secret stored in .beneath/secret
  client = get_client()

  # submit GraphQL query to get the projectID from the user-supplied projectName
  result = client.get_project_by_name(args.project)
  project_id = result['projectID']

  # read schema from the user-supplied file
  with open(args.file, "r") as f:
    schema = f.read()

  # submit GraphQL mutation to create an external stream
  result = client.create_external_stream(project_id=project_id, schema=schema, manual=args.manual, batch=args.batch)

  # print out the stream's details to the user
  pretty_print_graphql_result(result, [
    "name",
    "description",
    "external",
    "batch",
    "manual",
    "project",
    "keyFields",
  ])


def stream_update_ext(args):
  # start client with the secret stored in .beneath/secret
  client = get_client()

  # get stream id
  result = client.get_stream_details(project_name=args.project, stream_name=args.stream)
  stream_id = result['streamID']

  # read schema from the user-supplied file
  schema = None
  if args.file:
    with open(args.file, "r") as f:
      schema = f.read()

  # submit GraphQL mutation to update stream
  manual = args.manual
  result = client.update_external_stream(stream_id, schema, manual)

  # print out the stream's details to the user
  pretty_print_graphql_result(result, [
    "name",
    "description",
    "external",
    "batch",
    "manual",
    "project",
    "keyFields",
  ])


def stream_delete_ext(args):
  client = get_client()
  result = client.get_stream_details(project_name=args.project, stream_name=args.stream)
  stream_id = result['streamID']
  result = client.delete_external_stream(stream_id)
  pretty_print_graphql_result(result)


def stream_ext_batch_create(args):
  client = get_client()
  result = client.get_stream_details(project_name=args.project, stream_name=args.stream)
  stream_id = result['streamID']
  result = client.create_external_stream_batch(stream_id)
  pretty_print_graphql_result(result)


def stream_ext_batch_commit(args):
  client = get_client()
  result = client.commit_external_stream_batch(instance_id=args.instance)
  pretty_print_graphql_result(result)


def stream_ext_batches_clear(args):
  client = get_client()
  result = client.get_stream_details(project_name=args.project, stream_name=args.stream)
  stream_id = result['streamID']
  result = client.clear_pending_external_stream_batches(stream_id)
  pretty_print_graphql_result(result)


if __name__ == '__main__':
  main()
