from copy import deepcopy
from datetime import datetime
import pathlib
import os
import PySimpleGUI as sg
from typing import Optional, Union, Callable
from .uploader import Uploader, utils, RetryConfig, _DEFAULT_RETRY_CONFIG
from .layout import create_layout
from .__version__ import __version__
from . import config, auth
from loguru import logger
import sys


class App:
    def __init__(
        self,
        key: Optional[Union[str, bytes]],
        title: str = f"Google Cloud Storage Uploader - {__version__}",
        with_authentication: bool = True,
        with_service_account: bool = True,
        with_bucket_id: bool = True,
        with_password: bool = False,
        with_checkboxes: bool = False,
        dry_run: bool = False,
        service_account_path: pathlib.Path = config.service_account_path,
        client_secrets_path: pathlib.Path = config.client_secrets_path,
        user_credentials_path: pathlib.Path = config.get_user_credentials_path(),
        log_file_path: pathlib.Path = config.get_log_file_path(),
        icon: Optional[pathlib.Path] = config.get_icon_path(),
        prefix: Optional[Union[str, Callable[[], str]]] = None,
        retry_config: Optional[RetryConfig] = _DEFAULT_RETRY_CONFIG,
    ) -> None:

        logger.info(f"Starting app (version {__version__})")

        self.last_updated = datetime.now()
        self.key = key
        self.dry_run = dry_run
        self.service_account_path = pathlib.Path(service_account_path)
        self.client_secrets_path = pathlib.Path(client_secrets_path)
        self.user_credentials_path = pathlib.Path(user_credentials_path)
        self.log_file_path = log_file_path
        self._prefix = prefix
        self.retry_config = retry_config

        self.default_bucket = auth.default_bucket_from_service_account_file(
            self.service_account_path
        )
        self.window = sg.Window(
            title,
            create_layout(
                with_bucket_id,
                with_authentication,
                with_service_account,
                with_checkboxes,
                with_password,
                default_bucket=self.default_bucket,
            ),
            icon=icon,  # Yes, icon is needed here!
        )
        # Hack to remove icon from title bar, but is needed above on macos!
        if sys.platform == "darwin" and icon is not None:
            self.window.set_icon(None)

    @property
    def prefix(self):
        if self._prefix is None:
            return ""
        if callable(self._prefix):
            return self._prefix()
        return self._prefix

    def callback(self, stats):
        if stats.complete:
            self.window.write_event_value("UPLOADER-DONE", deepcopy(stats))
            return

        # Update progress at a lower rate
        now = datetime.now()
        if (now - self.last_updated).seconds > 0.3:
            self.last_updated = now
            self.window.write_event_value("UPLOADER-PROGRESS", deepcopy(stats))

    def update_progress(self, stats):

        perc = (
            int(100 * (stats.bytes_so_far / stats.total_bytes))
            if stats.total_bytes
            else 0
        )
        self.window["PROGRESS"].update(stats.bytes_so_far, stats.total_bytes, True)

        self.window["PROGRESS-TEXT"].update(
            f"{utils.display_bytes(stats.bytes_so_far)} of "
            f"{utils.display_bytes(stats.total_bytes)} ({perc}%)",
        )

        msg = [
            f"Uploaded: {stats.num_uploaded} of {stats.num_files} files",
            f"Skipped: {stats.num_skipped}",
            f"Errors: {stats.num_errors}",
            f"Elapsed time: {utils.display_time(stats.elapsed_time)}",
        ]

        if stats.complete:
            msg.append("Status: Inactive")
            self.window["START"].update(disabled=False)
            self.window["STOP"].update(disabled=True)
            self.window["AUTH"].update(disabled=False)
        else:
            msg.append("Status: Active")

        self.window["STATS"].update("\n".join(msg))

    def clear_status(self):
        self.window["STATS"].update("")
        self.window["PROGRESS-TEXT"].update("")
        self.window["PROGRESS"].update(0, 100, True)

    def display_error(self, msg=None):
        msg = "View the log file for details" if msg is None else msg
        msg = f"Error occurred:\n{msg}"
        self.window["FEEDBACK"].update(msg)
        self.clear_status()

    def run(self):

        uploader = None

        while True:
            event, values = self.window.read()

            if event == sg.WIN_CLOSED:
                if uploader is not None:
                    uploader.stop()
                break

            if event == "START":
                logger.info("Action: New upload started")

                # If SA file provided, use that over user credentials
                if os.path.isfile(values.get("SA-FILE", "")):
                    user_credentials_path = None
                    sa_file = values.get("SA-FILE")
                else:
                    user_credentials_path = self.user_credentials_path
                    sa_file = self.service_account_path
                if values.get("PW"):
                    key = auth.key_from_password(values.get("PW"), self.key)
                else:
                    key = self.key

                try:
                    uploader = Uploader(
                        bucket_id=values.get("BUCKET", self.default_bucket),
                        service_account_file=sa_file,
                        user_credentials_path=user_credentials_path,
                        key=key,
                        client_secrets_path=self.client_secrets_path,
                        dry_run=self.dry_run,
                        prefix=(
                            datetime.utcnow().isoformat()
                            if values.get("DATETIME-PREFIX")
                            else self.prefix
                        ),
                        delete_files_after_upload=values.get("DELETE", False),
                        retry_confg=self.retry_config,
                    )
                    uploader.start(
                        [
                            pathlib.Path(_)
                            for _ in values["PATHS"].split(sg.BROWSE_FILES_DELIMITER)
                        ],
                        self.callback,
                    )
                    self.last_updated = datetime.now()
                    dt = self.last_updated.strftime("%d, %b at %T")
                    self.window["FEEDBACK"].update(f"Upload started on {dt}")
                    self.window["START"].update(disabled=True)
                    self.window["STOP"].update(disabled=False)
                    self.window["AUTH"].update(disabled=True)
                    self.window["PROGRESS"].update(0, 100, True)
                except ValueError as e:
                    logger.exception("Value error during startup")
                    self.display_error(msg=e)
                    continue
                except Exception:
                    logger.exception("Uploader raised exception on startup")
                    self.display_error()
                    continue

            elif event == "STOP" and uploader is not None:
                logger.info("Action: stop current upload")
                self.window["STOP"].update(disabled=True)
                uploader.stop()
                self.window["FEEDBACK"].update("Stopping transfer...")

            elif event == "AUTH":
                logger.info("Action: execute user authentication")
                Uploader.run_oauth_flow(
                    self.user_credentials_path,
                    self.client_secrets_path,
                    self.key,
                )

            elif event == "VIEW-LOG":
                logger.info("Action: View log file")
                try:
                    utils.open(self.log_file_path)
                except Exception as e:
                    logger.exception(f"Failed to open {self.log_file_path}")
                    self.window["FEEDBACK"].update(e)
                    pass
            elif event == "UPLOADER-PROGRESS":
                self.update_progress(values.get("UPLOADER-PROGRESS"))
            elif event == "UPLOADER-DONE":
                self.update_progress(values.get("UPLOADER-DONE"))
                self.window["FEEDBACK"].update("")

        self.window.close()
