# Copyright 2022 The Debusine Developers
# See the AUTHORS file at the top-level directory of this distribution
#
# This file is part of Debusine. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution. No part of Debusine, including this file, may be copied,
# modified, propagated, or distributed except according to the terms
# contained in the LICENSE file.

"""Define interface of the file backend."""

import contextlib
import logging
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Optional

import requests

from debusine.db.models import File, FileInStore
from debusine.utils import NotSupportedError

logger = logging.getLogger(__name__)


class FileBackendInterface(ABC):
    """Interface to operate files in a storage."""

    def __init__(self):
        """Initialize object."""
        # self.db_store holds the FileStore in use
        self.db_store = None

    @abstractmethod
    def get_url(self, fileobj: File) -> Optional[str]:
        """Return a URL pointing to the content when possible or None."""
        raise NotImplementedError()

    @abstractmethod
    def get_local_path(self, fileobj: File) -> Optional[Path]:
        """Return the path to a local copy of the file when possible or None."""
        raise NotImplementedError()

    def remove_file(self, fileobj: File):
        """
        Remove the file from the FileStore.

        This removes the copy of the file identified by ``fileobj`` from the
        underlying storage and also the FileInStore entry indicating the
        availability of said file in the FileStore.
        """
        try:
            FileInStore.objects.get(store=self.db_store, file=fileobj).delete()
        except FileInStore.DoesNotExist:
            pass

        self._remove_file(fileobj)

    @abstractmethod
    def _remove_file(self, fileobj: File):
        """Remove the file pointed by fileobj from the underlying storage."""
        raise NotImplementedError()

    @abstractmethod
    def add_file(
        self, local_path: Path, fileobj: Optional[File] = None
    ) -> File:
        """
        Copy local_path to underlying storage and register it in the FileStore.

        The optional ``fileobj`` provides the File used to identify the content
        available in local_path.
        """
        raise NotImplementedError()

    @contextlib.contextmanager
    def get_stream(self, fileobj: File):
        """
        Return a file-like object that can be read.

        Try using the file from get_local_path(). Do not support RemoteFiles
        yet.
        """
        if (local_path := self.get_local_path(fileobj)) is not None:
            with open(local_path, "rb") as f:
                yield f
        elif (url := self.get_url(fileobj)) is not None:
            with requests.get(url, stream=True) as r:
                r.decode_content = True
                yield r.raw
        else:
            raise NotSupportedError(
                "This FileBackend doesn't support streaming"
            )
            # Note: this could fit a "always-redirect" remote file backend
