#!/usr/bin/env python3
import logging
import ntpath
from datetime import datetime
from typing import Dict, Iterable, Optional, Tuple
from uuid import UUID

import requests

from ..utils.retry import retry
from .constant import (
    INGEST_URL,
    PATH_TEMPLATES,
    RETRY_BASE_MS,
    RETRY_JITTER_MS,
    RETRY_STRATEGY,
    FileType,
)
from .env import get_blob_env
from .utils import iter_files

logger = logging.getLogger(__name__)

_EXCEPTIONS = (
    requests.exceptions.Timeout,
    requests.exceptions.ConnectTimeout,
)


def _path_and_url(
    source_id: UUID,
    file_type: FileType,
    file_path: str,
) -> Tuple[str, str]:

    now = datetime.utcnow()
    timestamp = int(now.timestamp())
    filename = ntpath.basename(file_path)
    path_template = PATH_TEMPLATES[file_type]
    path = path_template.format(
        timestamp=timestamp,
        source_id=source_id,
        filename=filename,
    )

    url = f"{INGEST_URL}/{path}"

    return path, url


def _headers(token: str) -> Dict:
    return {
        "Authorization": f"Token {token}",
        "Accept": "text/csv, application/json",
    }


def _upload(
    token: str,
    source_id: UUID,
    file_path: str,
    file_type: FileType,
) -> None:
    """
    Upload the given file to Google Cloud Storage (GCS)
    - Don't call GCS API directly
    - Call the ingestion proxy which handles authorisation and uploading
    """
    path, url = _path_and_url(source_id, file_type, file_path)
    headers = _headers(token)
    timeout, max_retries = get_blob_env()

    with open(file_path, "rb") as file_content:

        @retry(
            exceptions=_EXCEPTIONS,
            max_retries=max_retries,
            base_ms=RETRY_BASE_MS,
            jitter_ms=RETRY_JITTER_MS,
            strategy=RETRY_STRATEGY,
        )
        def _request_post():
            response = requests.post(
                url=url,
                headers=headers,
                files={"file": file_content},
                timeout=timeout,
            )
            response.raise_for_status()

        _request_post()

    logger.info(f"Uploaded {file_path} as {file_type.value} to {path}")


def upload_manifest(
    token: str,
    source_id: UUID,
    file_path: str,
) -> None:
    """
    credentials: path to file or dict
    source_id: id for the source
    file_path: path to the local manifest to upload
    """
    _upload(token, source_id, file_path, FileType.DBT)


def upload(
    token: str,
    source_id: UUID,
    file_type: FileType,
    file_path: Optional[str] = None,
    directory_path: Optional[str] = None,
) -> None:
    """
    credentials: path to file or dict
    source_id: id for the source
    file_type: type of file(s) uploaded - see FileType Enum
    file_path: path to the local visualization or warehouse file to upload
    directory_path: path to the local directory containing files to upload
    """
    files: Iterable[str]
    if directory_path:
        files = iter_files(directory_path)
    elif file_path:
        files = [file_path]
    else:
        message = "either file_path or directory_path should be defined"
        raise ValueError(message)

    for file_ in files:
        _upload(token, source_id, file_, file_type)
