from aiohttp import web
from aiohttp_session import get_session

from user import UserError
from user import LoginError, log_in, log_out
from user import SignupError, sign_up
from user import find_user_by_id
from user import get_user_videos, get_user_videos_count
from user import is_video_liked
from user import like_video, unlike_video
from user import upload_avatar, upload_video
from user import serialize_user
from user import get_authorized_user

from video import VideoError
from video import serialize_video
from video import get_random_video
from video import get_likes, get_tags

from comment import get_comments, get_comments_count, post_comment

from common import search_videos, suggest_tag

from tools import verify_captcha

from config import config

routes = web.RouteTableDef()

def report_error(error):
  return web.json_response({
    'error': str(error)
  })

@routes.post('/api/random')
async def api_random(request):
  video = await get_random_video()

  return web.json_response({
    'video_id': video.id
  })

@routes.post('/api/login')
async def api_login(request):
  session = await get_session(request)

  data = await request.post()
  username = data.get('username')
  password = data.get('password')
  hcaptcha_token = data.get('h-captcha-response')

  if not await verify_captcha(hcaptcha_token):
    return report_error('captcha check failed')

  try:
    user = await log_in(session, username, password)
  except (UserError, LoginError) as error:
    return report_error(error)
  
  return web.json_response({
    'user_id': user.id
  })

@routes.post('/api/signup')
async def api_signup(request):
  session = await get_session(request)

  data = await request.post()
  username = data.get('username')
  password = data.get('password')
  hcaptcha_token = data.get('h-captcha-response')

  if not await verify_captcha(hcaptcha_token):
    return report_error('captcha check failed')

  try:
    user = await sign_up(session, username, password, ip_address=request.remote)
  except (UserError, SignupError) as error:
    return report_error(error)
  
  return web.json_response({
    'user_id': user.id
  })

@routes.post('/api/logout')
async def api_logout(request):
  session = await get_session(request)

  try:
    await log_out(session)
  except UserError as error:
    return report_error(error)

  return web.json_response({
    'status': 'success'
  })

@routes.post('/api/user')
async def api_user(request):
  data = await request.post()
  user_id = data.get('user_id')

  try:
    user = await find_user_by_id(user_id)
  except UserError as error:
    return report_error(error)
 
  return web.json_response(await serialize_user(user))

@routes.post('/api/videos')
async def api_videos(request):
  data = await request.post()
  user_id = data.get('user_id')
  offset = data.get('offset')

  try:
    videos = await get_user_videos(user_id, offset=offset)
    videos_count = await get_user_videos_count(user_id)
  except (UserError, VideoError) as error:
    return report_error(error)

  return web.json_response({
    'videos': videos,
    'videos_count': videos_count
  })

@routes.post('/api/comments')
async def api_comments(request):
  data = await request.post()
  video_id = data.get('video_id')
  offset = data.get('offset')

  try:
    comments = await get_comments(video_id, offset=offset)
    comments_count = await get_comments_count(video_id)
  except VideoError as error:
    return report_error(error)

  return web.json_response({
    'comments': comments,
    'comments_count': comments_count
  })

@routes.post('/api/search')
async def api_search(request):
  data = await request.post()
  tags = data.get('tags')
  offset = data.get('offset')

  try:
    videos = await search_videos(tags, offset=offset)
  except SyntaxError as error: # SyntaxError is thrown by parse_tags.
    return report_error(error)

  return web.json_response({
    'videos': videos
  })

@routes.post('/api/suggest')
async def api_tags(request):
  data = await request.post()
  tag = data.get('tag')

  try:
    tags_list = await suggest_tag(tag)
  except SyntaxError as error:
    return report_error(error)

  return web.json_response({
    'tags_list': tags_list
  })
  
@routes.post('/api/tags')
async def api_tags(request):
  data = await request.post()
  video_id = data.get('video_id')

  try:
    tags_list = await get_tags(video_id)
  except VideoError as error:
    return report_error(error)

  return web.json_response({
    'tags_list': tags_list
  })

@routes.post('/api/liked')
async def api_liked(request):
  session = await get_session(request)

  data = await request.post()
  video_id = data.get('video_id')

  try:
    is_liked = await is_video_liked(session, video_id)
  except (UserError, VideoError) as error:
    return report_error(error)

  return web.json_response({
    'is_liked': is_liked
  })

@routes.post('/api/likes')
async def api_likes(request):
  data = await request.post()
  video_id = data.get('video_id')

  try:
    likes_count = await get_likes(video_id)
  except VideoError as error:
    return report_error(error)

  return web.json_response({
    'likes_count': likes_count
  })

@routes.post('/api/like')
async def api_like(request):
  session = await get_session(request)

  data = await request.post()
  video_id = data.get('video_id')

  try:
    await like_video(session, video_id)

    likes_count = await get_likes(video_id)
  except (UserError, VideoError) as error:
    return report_error(error)

  return web.json_response({
    'likes_count': likes_count
  })

@routes.post('/api/unlike')
async def api_unlike(request):
  session = await get_session(request)

  data = await request.post()
  video_id = data.get('video_id')

  try:
    await unlike_video(session, video_id)
    
    likes_count = await get_likes(video_id)
  except (UserError, VideoError) as error:
    return report_error(error)

  return web.json_response({
    'likes_count': likes_count
  })

@routes.post('/api/comment')
async def api_comment(request):
  session = await get_session(request)

  data = await request.post()
  video_id = data.get('video_id')
  text = data.get('text')

  try:
    comment = await post_comment(session, video_id, text)
  except (UserError, VideoError, ValueError) as error:
    return report_error(error)

  return web.json_response({
    'comment_id': comment.id
  })

@routes.post('/api/upload/avatar')
async def api_upload_avatar(request):
  session = await get_session(request)

  reader = await request.multipart()
  image = await reader.next()  

  try:
    await upload_avatar(session, image)
  
    user = await get_authorized_user(session)
  except UserError as error:
    return report_error(error)
 
  return web.json_response(await serialize_user(user))

@routes.post('/api/upload/video')
async def api_upload_video(request):
  session = await get_session(request)

  if 'authorized' not in session:
    return web.json_response({
      'error': 'not authorized.'
    })

  video = None
  tags = None
  hcaptcha_token = None

  reader = await request.multipart()

  while True:
    part = await reader.next()

    if not part:
      break

    if part.name == 'video':
      video = bytearray()
      size = 0

      while True:
        chunk = await part.read_chunk()

        if not chunk:
          break

        size += len(chunk)

        if size >= config.MAX_VIDEO_SIZE * 1024 * 1024:
          return report_error('illegal video')

        video += chunk
    elif part.name == 'tags':
      tags = await part.read_chunk()
      tags = tags.decode('ASCII')
    elif part.name == 'h-captcha-response':
      hcaptcha_token = await part.read_chunk()
      hcaptcha_token = hcaptcha_token.decode('ASCII')
   
  if not hcaptcha_token:
    return report_error('captcha check failed')

  if not await verify_captcha(hcaptcha_token):
    return report_error('captcha check failed')

  if not video:
    return report_error('illegal video')

  if not tags:
    return report_error('illegal tags')

  try:
    video = await upload_video(session, video, tags)
  except (UserError, VideoError, SyntaxError) as error:
    return report_error(error)

  return web.json_response({
    'video_id': video.id
  })