import base64
import json

from contextlib import contextmanager

import gitlab

from httmock import urlmatch, HTTMock

from buildtrigger.gitlabhandler import GitLabBuildTrigger
from util.morecollections import AttrDict


@urlmatch(netloc=r'fakegitlab')
def catchall_handler(url, request):
  return {'status_code': 404}


@urlmatch(netloc=r'fakegitlab', path=r'/api/v4/users$')
def users_handler(url, request):
  if not request.headers.get('Authorization') == 'Bearer foobar':
    return {'status_code': 401}

  if url.query.find('knownuser') < 0:
    return {
      'status_code': 200,
      'headers': {
        'Content-Type': 'application/json',
      },
      'content': json.dumps([]),
    }

  return {
    'status_code': 200,
    'headers': {
      'Content-Type': 'application/json',
    },
    'content': json.dumps([
      {
        "id": 1,
        "username": "knownuser",
        "name": "Known User",
        "state": "active",
        "avatar_url": "avatarurl",
        "web_url": "https://bitbucket.org/knownuser",
      },
    ]),
  }


@urlmatch(netloc=r'fakegitlab', path=r'/api/v4/user$')
def user_handler(_, request):
  if not request.headers.get('Authorization') == 'Bearer foobar':
    return {'status_code': 401}

  return {
    'status_code': 200,
    'headers': {
      'Content-Type': 'application/json',
    },
    'content': json.dumps({
      "id": 1,
      "username": "john_smith",
      "email": "john@example.com",
      "name": "John Smith",
      "state": "active",
    }),
  }


@urlmatch(netloc=r'fakegitlab', path=r'/api/v4/projects/foo%2Fbar$')
def project_handler(_, request):
  if not request.headers.get('Authorization') == 'Bearer foobar':
    return {'status_code': 401}

  return {
    'status_code': 200,
    'headers': {
      'Content-Type': 'application/json',
    },
    'content': json.dumps({
      "id": 4,
      "description": None,
      "default_branch": "master",
      "visibility": "private",
      "path_with_namespace": "someorg/somerepo",
      "ssh_url_to_repo": "git@example.com:someorg/somerepo.git",
      "web_url": "http://example.com/someorg/somerepo",
    }),
  }


@urlmatch(netloc=r'fakegitlab', path=r'/api/v4/projects/4/repository/tree$')
def project_tree_handler(_, request):
  if not request.headers.get('Authorization') == 'Bearer foobar':
    return {'status_code': 401}

  return {
    'status_code': 200,
    'headers': {
      'Content-Type': 'application/json',
    },
    'content': json.dumps([
      {
        "id": "a1e8f8d745cc87e3a9248358d9352bb7f9a0aeba",
        "name": "Dockerfile",
        "type": "tree",
        "path": "files/Dockerfile",
        "mode": "040000",
      },
    ]),
  }


@urlmatch(netloc=r'fakegitlab', path=r'/api/v4/projects/4/repository/tags$')
def project_tags_handler(_, request):
  if not request.headers.get('Authorization') == 'Bearer foobar':
    return {'status_code': 401}

  return {
    'status_code': 200,
    'headers': {
      'Content-Type': 'application/json',
    },
    'content': json.dumps([
      {
        'name': 'sometag',
        'commit': {
          'id': '60a8ff033665e1207714d6670fcd7b65304ec02f',
        },
      },
      {
        'name': 'someothertag',
        'commit': {
          'id': '60a8ff033665e1207714d6670fcd7b65304ec02f',
        },
      },
    ]),
  }


@urlmatch(netloc=r'fakegitlab', path=r'/api/v4/projects/4/repository/branches$')
def project_branches_handler(_, request):
  if not request.headers.get('Authorization') == 'Bearer foobar':
    return {'status_code': 401}

  return {
    'status_code': 200,
    'headers': {
      'Content-Type': 'application/json',
    },
    'content': json.dumps([
      {
        'name': 'master',
        'commit': {
          'id': '60a8ff033665e1207714d6670fcd7b65304ec02f',
        },
      },
      {
        'name': 'otherbranch',
        'commit': {
          'id': '60a8ff033665e1207714d6670fcd7b65304ec02f',
        },
      },
    ]),
  }


@urlmatch(netloc=r'fakegitlab', path=r'/api/v4/projects/4/repository/branches/master$')
def project_branch_handler(_, request):
  if not request.headers.get('Authorization') == 'Bearer foobar':
    return {'status_code': 401}

  return {
    'status_code': 200,
    'headers': {
      'Content-Type': 'application/json',
    },
    'content': json.dumps({
      "name": "master",
      "merged": True,
      "protected": True,
      "developers_can_push": False,
      "developers_can_merge": False,
      "commit": {
        "author_email": "john@example.com",
        "author_name": "John Smith",
        "authored_date": "2012-06-27T05:51:39-07:00",
        "committed_date": "2012-06-28T03:44:20-07:00",
        "committer_email": "john@example.com",
        "committer_name": "John Smith",
        "id": "60a8ff033665e1207714d6670fcd7b65304ec02f",
        "short_id": "7b5c3cc",
        "title": "add projects API",
        "message": "add projects API",
        "parent_ids": [
          "4ad91d3c1144c406e50c7b33bae684bd6837faf8",
        ],
      },
    }),
  }


@urlmatch(netloc=r'fakegitlab', path=r'/api/v4/namespaces/someorg$')
def namespace_handler(_, request):
  if not request.headers.get('Authorization') == 'Bearer foobar':
    return {'status_code': 401}

  return {
    'status_code': 200,
    'headers': {
      'Content-Type': 'application/json',
    },
    'content': json.dumps({
      "id": 2,
      "name": "someorg",
      "path": "someorg",
      "kind": "group",
      "full_path": "someorg",
      "parent_id": None,
      "members_count_with_descendants": 2
    }),
  }


@urlmatch(netloc=r'fakegitlab', path=r'/api/v4/namespaces/knownuser$')
def user_namespace_handler(_, request):
  if not request.headers.get('Authorization') == 'Bearer foobar':
    return {'status_code': 401}

  return {
    'status_code': 200,
    'headers': {
      'Content-Type': 'application/json',
    },
    'content': json.dumps({
      "id": 2,
      "name": "knownuser",
      "path": "knownuser",
      "kind": "user",
      "full_path": "knownuser",
      "parent_id": None,
      "members_count_with_descendants": 2
    }),
  }


@urlmatch(netloc=r'fakegitlab', path=r'/api/v4/namespaces(/)?$')
def namespaces_handler(_, request):
  if not request.headers.get('Authorization') == 'Bearer foobar':
    return {'status_code': 401}

  return {
    'status_code': 200,
    'headers': {
      'Content-Type': 'application/json',
    },
    'content': json.dumps([{
      "id": 2,
      "name": "someorg",
      "path": "someorg",
      "kind": "group",
      "full_path": "someorg",
      "parent_id": None,
      "members_count_with_descendants": 2
    }]),
  }


def get_projects_handler(add_permissions_block):
  @urlmatch(netloc=r'fakegitlab', path=r'/api/v4/groups/2/projects$')
  def projects_handler(_, request):
    if not request.headers.get('Authorization') == 'Bearer foobar':
      return {'status_code': 401}

    permissions_block = {
      "project_access": {
        "access_level": 10,
        "notification_level": 3
      },
      "group_access": {
        "access_level": 20,
        "notification_level": 3
      },
    }

    return {
      'status_code': 200,
      'headers': {
        'Content-Type': 'application/json',
      },
      'content': json.dumps([{
        "id": 4,
        "name": "Some project",
        "description": None,
        "default_branch": "master",
        "visibility": "private",
        "path": "someproject",
        "path_with_namespace": "someorg/someproject",
        "last_activity_at": "2013-09-30T13:46:02Z",
        "web_url": "http://example.com/someorg/someproject",
        "permissions": permissions_block if add_permissions_block else None,
      },
      {
        "id": 5,
        "name": "Another project",
        "description": None,
        "default_branch": "master",
        "visibility": "public",
        "path": "anotherproject",
        "path_with_namespace": "someorg/anotherproject",
        "last_activity_at": "2013-09-30T13:46:02Z",
        "web_url": "http://example.com/someorg/anotherproject",
      }]),
    }
  return projects_handler


def get_group_handler(null_avatar):
  @urlmatch(netloc=r'fakegitlab', path=r'/api/v4/groups/2$')
  def group_handler(_, request):
    if not request.headers.get('Authorization') == 'Bearer foobar':
      return {'status_code': 401}

    return {
      'status_code': 200,
      'headers': {
        'Content-Type': 'application/json',
      },
      'content': json.dumps({
        "id": 1,
        "name": "SomeOrg Group",
        "path": "someorg",
        "description": "An interesting group",
        "visibility": "public",
        "lfs_enabled": True,
        "avatar_url": 'avatar_url' if not null_avatar else None,
        "web_url": "http://gitlab.com/groups/someorg",
        "request_access_enabled": False,
        "full_name": "SomeOrg Group",
        "full_path": "someorg",
        "parent_id": None,
      }),
    }
  return group_handler


@urlmatch(netloc=r'fakegitlab', path=r'/api/v4/projects/4/repository/files/Dockerfile$')
def dockerfile_handler(_, request):
  if not request.headers.get('Authorization') == 'Bearer foobar':
    return {'status_code': 401}

  return {
    'status_code': 200,
    'headers': {
      'Content-Type': 'application/json',
    },
    'content': json.dumps({
      "file_name": "Dockerfile",
      "file_path": "Dockerfile",
      "size": 10,
      "encoding": "base64",
      "content": base64.b64encode('hello world'),
      "ref": "master",
      "blob_id": "79f7bbd25901e8334750839545a9bd021f0e4c83",
      "commit_id": "d5a3ff139356ce33e37e73add446f16869741b50",
      "last_commit_id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d"
    }),
  }


@urlmatch(netloc=r'fakegitlab', path=r'/api/v4/projects/4/repository/files/somesubdir%2FDockerfile$')
def sub_dockerfile_handler(_, request):
  if not request.headers.get('Authorization') == 'Bearer foobar':
    return {'status_code': 401}

  return {
    'status_code': 200,
    'headers': {
      'Content-Type': 'application/json',
    },
    'content': json.dumps({
      "file_name": "Dockerfile",
      "file_path": "somesubdir/Dockerfile",
      "size": 10,
      "encoding": "base64",
      "content": base64.b64encode('hi universe'),
      "ref": "master",
      "blob_id": "79f7bbd25901e8334750839545a9bd021f0e4c83",
      "commit_id": "d5a3ff139356ce33e37e73add446f16869741b50",
      "last_commit_id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d"
    }),
  }


@urlmatch(netloc=r'fakegitlab', path=r'/api/v4/projects/4/repository/tags/sometag$')
def tag_handler(_, request):
  if not request.headers.get('Authorization') == 'Bearer foobar':
    return {'status_code': 401}

  return {
    'status_code': 200,
    'headers': {
      'Content-Type': 'application/json',
    },
    'content': json.dumps({
      "name": "sometag",
      "message": "some cool message",
      "target": "60a8ff033665e1207714d6670fcd7b65304ec02f",
      "commit": {
        "id": "60a8ff033665e1207714d6670fcd7b65304ec02f",
        "short_id": "60a8ff03",
        "title": "Initial commit",
        "created_at": "2017-07-26T11:08:53.000+02:00",
        "parent_ids": [
          "f61c062ff8bcbdb00e0a1b3317a91aed6ceee06b"
        ],
        "message": "v5.0.0\n",
        "author_name": "Arthur Verschaeve",
        "author_email": "contact@arthurverschaeve.be",
        "authored_date": "2015-02-01T21:56:31.000+01:00",
        "committer_name": "Arthur Verschaeve",
        "committer_email": "contact@arthurverschaeve.be",
        "committed_date": "2015-02-01T21:56:31.000+01:00"
      },
      "release": None,
    }),
  }


@urlmatch(netloc=r'fakegitlab', path=r'/api/v4/projects/foo%2Fbar/repository/commits/60a8ff033665e1207714d6670fcd7b65304ec02f$')
def commit_handler(_, request):
  if not request.headers.get('Authorization') == 'Bearer foobar':
    return {'status_code': 401}

  return {
    'status_code': 200,
    'headers': {
      'Content-Type': 'application/json',
    },
    'content': json.dumps({
      "id": "60a8ff033665e1207714d6670fcd7b65304ec02f",             
      "short_id": "60a8ff03366",
      "title": "Sanitize for network graph",
      "author_name": "someguy",
      "author_email": "some.guy@gmail.com",
      "committer_name": "Some Guy",
      "committer_email": "some.guy@gmail.com",
      "created_at": "2012-09-20T09:06:12+03:00",
      "message": "Sanitize for network graph",
      "committed_date": "2012-09-20T09:06:12+03:00",
      "authored_date": "2012-09-20T09:06:12+03:00",
      "parent_ids": [
        "ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba"
      ],
      "last_pipeline" : {
        "id": 8,
        "ref": "master",
        "sha": "2dc6aa325a317eda67812f05600bdf0fcdc70ab0",
        "status": "created",
      },
      "stats": {
        "additions": 15,
        "deletions": 10,
        "total": 25
      },
      "status": "running"
    }),
  }


@urlmatch(netloc=r'fakegitlab', path=r'/api/v4/projects/4/deploy_keys$', method='POST')
def create_deploykey_handler(_, request):
  if not request.headers.get('Authorization') == 'Bearer foobar':
    return {'status_code': 401}

  return {
    'status_code': 200,
    'headers': {
      'Content-Type': 'application/json',
    },
    'content': json.dumps({
      "id": 1,
      "title": "Public key",
      "key": "ssh-rsa some stuff",
      "created_at": "2013-10-02T10:12:29Z",
      "can_push": False,
    }),
  }


@urlmatch(netloc=r'fakegitlab', path=r'/api/v4/projects/4/hooks$', method='POST')
def create_hook_handler(_, request):
  if not request.headers.get('Authorization') == 'Bearer foobar':
    return {'status_code': 401}

  return {
    'status_code': 200,
    'headers': {
      'Content-Type': 'application/json',
    },
    'content': json.dumps({
      "id": 1,
      "url": "http://example.com/hook",
      "project_id": 4,
      "push_events": True,
      "issues_events": True,
      "confidential_issues_events": True,
      "merge_requests_events": True,
      "tag_push_events": True,
      "note_events": True,
      "job_events": True,
      "pipeline_events": True,
      "wiki_page_events": True,
      "enable_ssl_verification": True,
      "created_at": "2012-10-12T17:04:47Z",
    }),
  }


@urlmatch(netloc=r'fakegitlab', path=r'/api/v4/projects/4/hooks/1$', method='DELETE')
def delete_hook_handler(_, request):
  if not request.headers.get('Authorization') == 'Bearer foobar':
    return {'status_code': 401}

  return {
    'status_code': 200,
    'headers': {
      'Content-Type': 'application/json',
    },
    'content': json.dumps({}),
  }


@urlmatch(netloc=r'fakegitlab', path=r'/api/v4/projects/4/deploy_keys/1$', method='DELETE')
def delete_deploykey_handker(_, request):
  if not request.headers.get('Authorization') == 'Bearer foobar':
    return {'status_code': 401}

  return {
    'status_code': 200,
    'headers': {
      'Content-Type': 'application/json',
    },
    'content': json.dumps({}),
  }


@urlmatch(netloc=r'fakegitlab', path=r'/api/v4/users/2/projects$')
def user_projects_list_handler(_, request):
  if not request.headers.get('Authorization') == 'Bearer foobar':
    return {'status_code': 401}

  return {
    'status_code': 200,
    'headers': {
      'Content-Type': 'application/json',
    },
    'content': json.dumps([
      {
        "id": 2,
        "name": "Another project",
        "description": None,
        "default_branch": "master",
        "visibility": "public",
        "path": "anotherproject",
        "path_with_namespace": "knownuser/anotherproject",
        "last_activity_at": "2013-09-30T13:46:02Z",
        "web_url": "http://example.com/knownuser/anotherproject",
      }
    ]),
  }


@contextmanager
def get_gitlab_trigger(dockerfile_path='', add_permissions=True, missing_avatar_url=False):
  handlers = [user_handler, users_handler, project_branches_handler, project_tree_handler,
              project_handler, get_projects_handler(add_permissions), tag_handler,
              project_branch_handler, get_group_handler(missing_avatar_url), dockerfile_handler,
              sub_dockerfile_handler, namespace_handler, user_namespace_handler, namespaces_handler,
              commit_handler, create_deploykey_handler, delete_deploykey_handker,
              create_hook_handler, delete_hook_handler, project_tags_handler, 
              user_projects_list_handler, catchall_handler]

  with HTTMock(*handlers):
    trigger_obj = AttrDict(dict(auth_token='foobar', id='sometrigger'))
    trigger = GitLabBuildTrigger(trigger_obj, {
      'build_source': 'foo/bar',
      'dockerfile_path': dockerfile_path,
      'username': 'knownuser'
    })

    client = gitlab.Gitlab('http://fakegitlab', oauth_token='foobar', timeout=20, api_version=4)
    client.auth()

    trigger._get_authorized_client = lambda: client
    yield trigger