import os, sys
import json
import logging
from uuid import uuid4
from datetime import datetime
from base64 import b64encode, b64decode

from harvest.drivers.dynamodb_driver import DynamoDbDriver
from harvest.drivers.s3_driver import S3Driver
from harvest.controllers.place_controller import PlaceController
from harvest.controllers.role_controller import RoleController
from harvest.exception import *

BUCKET_NAME="product-harvest-photos"

class PhotoController(DynamoDbDriver):

  logger = logging.getLogger(__name__)
  logger.addHandler(logging.NullHandler())
  logger.setLevel(logging.DEBUG)

  def __init__(self, host, port):
    super().__init__(host, port, "Targets")
    super().set_partition_key("project_id")
    super().set_sort_key("target_id")
    self.place = PlaceController(host, port)
    self.role = RoleController(host, port)
    self.__s3 = S3Driver(BUCKET_NAME)

  def set_project_id(self, project_id):
    self.project_id = project_id
    self.place.set_project_id(self.project_id)
    self.role.set_project_id(project_id)
  
  def set_user_id(self, user_id):
    self.user_id = user_id

  def set_target_id(self, target_id):
    self.target_id = str(target_id)

  def set_photo_id(self, photo_id):
    self.photo_id = photo_id

  #target単体配下の写真全てを取得
  def list(self, target_id):
    if not self.project_id or not target_id:
      raise ValueError

    target = super().get_item(self.project_id, target_id)["Item"]
    bfr_photos = target["photos"]["before"]
    aft_photos = target["photos"]["after"]

    # TODO: s3 get_object処理
    ret = {
      "before": [
        # base64 photo
      ],
      "after": [
        # base64 photo
      ]
    }
    if "adopt" in target["photos"]:
      ret.update(target["photos"]["adopt"])
    return ret

  #target単体配下の写真単体を取得
  def show(self, photo_id, encode=False):
    if not self.project_id:
      raise ValueError
    if not photo_id:
      target_id = self.target_id
    # TODO: s3 get_object処理
    ret = self.__s3.get_obj_bytes(photo_id)
    if encode:
      encoded = b64encode(ret)
      ret = encoded.decode("ascii")
    return ret

  #target単体配下に写真を追加
  def create(self, target_id, type, data):
    if not self.project_id or not type or not data:
      raise ValueError
    if not type in ["after", "before"]:
      raise ValueError
    if not isinstance(data, bytes):
      raise ValueError
    
    new_id = str(uuid4())
    now = datetime.now().isoformat()
    # TODO: s3 put_objectで画像を保存
    ret = self.__s3.put_obj(new_id, data, "project_id={}&user_id={}&target_id={}&type={}&created_at={}".format(self.project_id, self.user_id, target_id, type, now))
    # 何が返ってくればOK？例外処理が必要
    self.logger.info(ret)

    #target_idを使ってtargetを取得
    target = super().get_item(self.project_id, target_id)["Item"]

    if len(target["photos"][type]) == 5:
      # 5枚まで保存可能な仕様なので、現在5枚なら画像保存はしない
      raise PhotoSaveLimitExceededError()

    if len(target["photos"][type]) > 0:
      # 既に写真が存在するなら、上位階層たちのresultsは変更しない
      return super().update_item(
          partition_key = self.project_id,
          sort_key = target_id,
          update_exp = 'set photos.#type = list_append(photos.#type, :id_list), photos.adopt.#type = :id, #updated_at = :updated_at, #updated_by = :updated_by',
          exp_attr_names = {
              "#type": type,
              "#updated_at": "updated_at",
              "#updated_by": "updated_by"
          },
          exp_attr_values={
              ":id_list": [new_id],
              ":id": new_id,
              ":updated_at": now,
              ":updated_by": self.user_id

          },
          cond_exp = 'attribute_exists(project_id)'
      )

    elif len(target["photos"][type]) == 0:
      if target["parent_place_id"] == self.project_id:
        #parent_place_id = self.project_id
        hierarchy = None
      else:
        # 親placeを取得
        parent = self.place.show(target["parent_place_id"])
        hierarchy = parent["hierarchy"]
        # ヒエラルキに親place_idを足す
        hierarchy += "#{}".format(target["parent_place_id"])
        hierarchy = hierarchy.split("#")
        if hierarchy[0] == self.project_id:
          del hierarchy[0]

      transact_items = []
      if hierarchy:
        for place_id in hierarchy:
          transact_items.append(
              {
                'Update': {
                  'TableName': "Places",
                  'Key': {
                    'project_id': {'S': self.project_id},
                    'place_id': {'S': place_id}
                  },
                  'ConditionExpression': 'attribute_exists(project_id)',
                  'UpdateExpression': 'set photos.results.#type = photos.results.#type + :p, #updated_at = :updated_at, #updated_by = :updated_by',
                  'ExpressionAttributeValues': {
                      ":p": {"N": "1"},
                      ":updated_at": {"S": now},
                      ":updated_by": {"S": self.user_id}
                  },
                  'ExpressionAttributeNames':  {
                      "#type": type,
                      "#updated_at": "updated_at",
                      "#updated_by": "updated_by"
                  }
                }
              }
          )
      transact_items.append(
          {
            'Update': {
              'TableName': "Targets",
              'Key': {
                'project_id': {'S': self.project_id},
                'target_id': {'S': target_id}
              },
              'ConditionExpression': 'attribute_exists(project_id)',
              'UpdateExpression': 'set photos.#type = list_append(photos.#type, :id_list), photos.adopt.#type = :id, #updated_at = :updated_at, #updated_by = :updated_by',
              'ExpressionAttributeValues': {
                  ":id_list": {"L": [{"S": new_id}]},
                  ":id": {"S": new_id},
                  ":updated_at": {"S": now},
                  ":updated_by": {"S": self.user_id}
              },
              'ExpressionAttributeNames':  {
                  "#type": type,
                  "#updated_at": "updated_at",
                  "#updated_by": "updated_by"
              }
            }
          }
      )

      ret = self.client.transact_write_items(
          ReturnConsumedCapacity = 'INDEXES',
          TransactItems = transact_items
      )
      self.logger.info("transact_write_items result: {}".format(str(ret)))
      return ret

  #写真を採用
  def update_adopt(self, target_id, type, photo_id):
    if not self.project_id or not target_id or not type or not photo_id:
      raise ValueError

    # user_idを使って権限チェック
    now = datetime.now().isoformat()
    return super().update_item(
        partition_key = self.project_id,
        sort_key = target_id,
        update_exp = "set photos.adopt.#type = :photo_id, #updated_at = :updated_at, #updated_by = :updated_by",
        exp_attr_names = {
            '#type': type,
            "#updated_at": "updated_at",
            "#updated_by": "updated_by"
        },
        exp_attr_values={
            ":photo_id": photo_id,
            ":updated_at": now,
            ":updated_by": self.user_id
        },
        cond_exp = 'attribute_exists(project_id)'
    )

  #target単体配下の写真単体を削除
  def delete(self, target_id, photo_id):
    if not self.project_id or not self.target_id:
      raise ValueError

    type = ""
    # target_idを使ってtargetを取得
    target = super().get_item(self.project_id, target_id)["Item"]

    # photo listからphoto_idがなんのtypeか調べる
    # 配列の中から対象の写真の位置を調べる
    for key in ["before", "after"]:
      if photo_id in target["photos"][key]:
        index = target["photos"][key].index(photo_id)
        type = key
        break
    else:
      # 指定されたtargetの中に存在しないphoto_id
      raise PhotoIsNotFoundError()

    # 最後の一枚なら消さない
    if len(target["photos"][type]) == 1:
      raise LastOnePhotoIsCanNotDeleteError()

    # 採用写真なら消さない
    if target["photos"]["adopt"][type] == photo_id:
      raise AdoptedPhotoIsCanNotDeleteError()

    # targetをupdate
    now = datetime.now().isoformat()
    # 既に写真が存在するなら、上位階層たちのresultsは変更しない
    update_exp = 'remove photos.#type[%d] set #updated_at = :updated_at, #updated_by = :updated_by' % (index)
    super().update_item(
        partition_key = self.project_id,
        sort_key = target_id,
        update_exp = update_exp,
        exp_attr_names = {
            "#type": type,
            "#updated_at": "updated_at",
            "#updated_by": "updated_by"
        },
        exp_attr_values={
            ":updated_at": now,
            ":updated_by": self.user_id

        },
        cond_exp = 'attribute_exists(project_id)'
    )

    # s3からphotoを消す
    ret = self.__s3.delete_obj(photo_id)

    return ret
