#! /usr/bin/env python3.6
# coding: utf-8
from kamera.logger import log

from pathlib import Path
import datetime as dt
from functools import partial

import pytz
from timezonefinderL import TimezoneFinder
import dropbox
import requests

from kamera import config
from kamera import image_processing
from kamera.mediatypes import KameraEntry

from typing import Callable, Generator, Optional


class Cloud:
    def __init__(self):
        self.dbx = dropbox.Dropbox(config.DBX_TOKEN)
        self.dbx.users_get_current_account()

    def list_entries(self, path: Path) -> Generator[KameraEntry, None, None]:
        result = self.dbx.files_list_folder(
            path=path.as_posix(),
            include_media_info=True
        )
        while True:
            log.info(f"Entries in upload folder: {len(result.entries)}")
            for entry in result.entries:
                # Ignore deleted files, folders
                if not (entry.path_lower.endswith(config.media_extensions) and
                        isinstance(entry, dropbox.files.FileMetadata)):
                    continue

                dbx_photo_metadata = entry.media_info.get_metadata() if entry.media_info else None
                kamera_entry = KameraEntry(entry, dbx_photo_metadata)
                yield kamera_entry

            # Repeat only if there's more to do
            if result.has_more:
                result = self.dbx.files_list_folder_continue(result.cursor)
            else:
                break

    def _get_folder_name(self, month: int) -> str:
        try:
            return config["folder_names"][str(month)]
        except KeyError:
            return str(month)

    def _execute_transfer(self, transfer_func: Callable, destination: Path):
        try:
            transfer_func()
        except requests.exceptions.SSLError:
            log.info("Encountered SSL error during transfer. Trying again")
            transfer_func()
        except dropbox.exceptions.BadInputError:
            log.info(f"Making folder: {destination}")
            self.dbx.files_create_folder(destination.as_posix())
            transfer_func()

    def move_entry(
            self,
            from_path: Path,
            out_dir: Path,
            date: Optional[dt.datetime] = None
    ):
        if date is not None:
            destination = out_dir / str(date.year) / self._get_folder_name(date.month) / from_path.name
        else:
            destination = out_dir / from_path.name

        transfer_func = partial(
            self.dbx.files_move,
            from_path=from_path.as_posix(),
            to_path=destination.as_posix(),
            autorename=True
        )

        log.info(f"{from_path.stem}: Moving to dest: {destination}")
        self._execute_transfer(transfer_func, destination)

    def copy_entry(
            self,
            from_path: Path,
            out_dir: Path,
            date: dt.datetime
    ):
        destination = out_dir / str(date.year) / self._get_folder_name(date.month) / from_path.name

        transfer_func = partial(
            self.dbx.files_copy,
            from_path=from_path.as_posix(),
            to_path=destination.as_posix(),
            autorename=True
        )

        log.info(f"{from_path.stem}: Copying to dest: {destination}")
        self._execute_transfer(transfer_func, destination)

    def upload_entry(
            self,
            from_path: Path,
            new_data: bytes,
            out_dir: Path,
            date: dt.datetime
    ):
        new_name = from_path.with_suffix(".jpg").name
        destination = out_dir / str(date.year) / self._get_folder_name(date.month) / new_name

        transfer_func = partial(
            self.dbx.files_upload,
            f=new_data,
            path=destination.as_posix(),
            autorename=True
        )

        log.info(f"{destination.stem}: Uploading to dest: {destination}")
        self._execute_transfer(transfer_func, destination)

    def download_entry(self, path_str: str):
        try:
            return self.dbx.files_download(path_str)
        except requests.exceptions.SSLError:
            log.info("Encountered SSL error during transfer. Trying again")
            return self.dbx.files_download(path_str)


def parse_date(entry: KameraEntry) -> dt.datetime:
    naive_date = entry.time_taken if entry.time_taken is not None else entry.client_modified
    utc_date = naive_date.replace(tzinfo=dt.timezone.utc)
    if entry.location is not None:
        img_tz = TimezoneFinder().timezone_at(
            lat=entry.location.latitude,
            lng=entry.location.longitude
        )
        if img_tz:
            local_date = utc_date.astimezone(tz=pytz.timezone(img_tz))
            return local_date
    local_date = utc_date.astimezone(tz=pytz.timezone(config.settings["default_tz"]))
    return local_date


def process_entry(
        entry: KameraEntry,
        cloud: Cloud,
        out_dir: Path,
        backup_dir: Path,
        error_dir: Path):
    log.info(f"{entry}: Processing")
    start_time = dt.datetime.now()
    try:
        date = parse_date(entry)
        if entry.path.suffix.lower() in config.video_extensions:
            cloud.copy_entry(entry.path, out_dir, date)
        elif entry.path.suffix.lower() in config.image_extensions:
            _, response = cloud.download_entry(entry.path.as_posix())
            new_data = image_processing.main(
                data=response.raw.data,
                filepath=entry.path,
                date=date,
                location=entry.location,
                dimensions=entry.dimensions,
            )

            if new_data is None:
                cloud.copy_entry(entry.path, out_dir, date)
            else:
                cloud.upload_entry(entry.path, new_data, out_dir, date)
        else:
            return
        cloud.move_entry(entry.path, out_dir=backup_dir, date=date)
    except Exception:
        log.exception(f"Exception occured, moving to Error subfolder: {entry}")
        cloud.move_entry(entry.path, out_dir=error_dir)
    finally:
        end_time = dt.datetime.now()
        duration = end_time - start_time
        log.info(f"{entry}, duration: {duration}")
        log.info("\n")


def run_once(
        cloud: Cloud,
        in_dir: Path,
        out_dir: Path,
        backup_dir: Path,
        error_dir: Path,
) -> None:
    entries = cloud.list_entries(in_dir)
    for entry in entries:
        process_entry(
            cloud=cloud,
            entry=entry,
            out_dir=out_dir,
            backup_dir=backup_dir,
            error_dir=error_dir,
        )

@@ -1733,16 +1733,23 @@
 tination
+_folder
 : Path):
@@ -1745,24 +1745,24 @@
 der: Path):%0A
-
         try:
@@ -2030,32 +2030,39 @@
 er: %7Bdestination
+_folder
 %7D%22)%0A            
@@ -2093,32 +2093,39 @@
 lder(destination
+_folder
 .as_posix())%0A   
@@ -2808,32 +2808,39 @@
 unc, destination
+.parent
 )%0A%0A    def copy_
@@ -3370,32 +3370,39 @@
 unc, destination
+.parent
 )%0A%0A    def uploa
@@ -3937,32 +3937,32 @@
 %7Bdestination%7D%22)%0A
-
         self._ex
@@ -4002,16 +4002,23 @@
 tination
+.parent
 )%0A%0A    d

use folder name instead of file name in dbx.files_create_folder