import datetime
import logging
import os
import re
import shutil

from os.path import dirname
from pathlib import Path
from sys import getfilesystemencoding

from django.core.exceptions import PermissionDenied
from django.utils.decorators import method_decorator
from django.utils.functional import cached_property

from django_http_auth.decorators import http_basic_auth

from django.http import HttpResponseForbidden, HttpResponseNotAllowed, HttpResponseBadRequest, \
    HttpResponseRedirect, Http404, HttpResponse, FileResponse

from djangodav.acls import DavAcl, ReadOnlyAcl, FullAcl
from djangodav.base.resources import BaseDavResource, MetaEtagMixIn
from djangodav.fs.resources import DummyFSDAVResource
from djangodav.locks import DummyLock
from djangodav.utils import url_join
from djangodav.views import DavView

from procat2.models import Catalog
from procat2.settings import ASSET_DIR

from .utils import clean_path, ensure_dir, set_file_perms, WORKDIR
from .tasks import process_markup_pdf

log = logging.getLogger(__name__)

fs_encoding = getfilesystemencoding()


@method_decorator(http_basic_auth, name='dispatch')
class MarkupDavView(DavView):

    def __init__(self):
        super().__init__(resource_class=resource_factory, lock_class=DummyLock, acl_class=FullAcl)

    def get_access(self, resource):
        return resource.get_acl()


def resource_factory(**kwargs):
    # log.info(f"resource_factory '{kwargs}'")
    path = kwargs['path']
    if path == RootFolderResource.PATH:
        return RootFolderResource(**kwargs)
    elif path == CatalogFolderResource.PATH:
        return CatalogFolderResource(**kwargs)
    elif is_my_cats_catalog(path):
        return CatalogResource(**kwargs)
    elif is_markup_catalog(path):
        return MarkupResource(**kwargs)
    else:
        return NonsensicalResource(**kwargs)

def is_my_cats_catalog(path):
    return re.search(fr'^{CatalogFolderResource.PATH}.*-([0-9]+)\.pdf', path, re.I)

def is_markup_catalog(path):
    return re.search(fr'^{MarkupResource.PATH}', path, re.I)


class MarkupDavResource(MetaEtagMixIn, BaseDavResource):

    def __str__(self):
        return f"<{type(self).__name__} '{self.user}' '{self.path}'>"

    def get_acl(self):
        return ReadOnlyAcl()
        # return DavAcl(read=True, write=False, delete=False, full=None)


class RootFolderResource(MarkupDavResource):
    NAME = ''
    PATH = '/'

    def get_parent(self):
        return None

    def get_children(self):
        children = [CatalogFolderResource(user=self.user),
                    MarkupResource(user=self.user),
        ]
        for child in children:
            yield child

    @property
    def getcontentlength(self):
        return 2

    def get_created(self):
        return datetime.datetime.now() # TODO last created time of object in there?

    def get_modified(self):
        return datetime.datetime.now() # TODO last modified time of object in there?

    @property
    def is_collection(self):
        return True

    @property
    def is_object(self):
        return False

    @property
    def exists(self):
        return True


class CatalogFolderResource(MarkupDavResource):
    NAME = 'My Catalogs'
    PATH = f'/{NAME}/'

    def __init__(self, path=PATH, user=None):
        super().__init__(path=path, user=user)

    def get_parent(self):
        return RootFolderResource(user=self.user)

    def get_children(self):
        for child in self.user_catalogs:
            yield CatalogResource(path=url_join(*(self.path + [child])), user=self.user)

    @cached_property
    def user_catalogs(self):
        cats = Catalog.objects.filter(owner=self.user).order_by('-updated')
        return [c.pdf_name() for c in cats if c.pdf_exists()]

    @property
    def getcontentlength(self):
        return 2 # TODO based on contents

    def get_created(self):
        return datetime.datetime.now() # TODO last created time of object in there?

    def get_modified(self):
        return datetime.datetime.now() # TODO last modified time of object in there?

    @property
    def is_collection(self):
        return True

    @property
    def is_object(self):
        return False

    @property
    def exists(self):
        return True


class CatalogResource(MarkupDavResource):

    def get_parent(self):
        return CatalogFolderResource(user=self.user)

    def get_children(self):
        return
        yield

    @cached_property
    def catalog(self):
        id = self.id_from_pdf_name(self.path)
        return Catalog.objects.get(id=id)

    def id_from_pdf_name(self, path):
        result = re.search(r'-([0-9]+)\.pdf', path[-1], re.I)
        if result and result.groups():
            return result.group(1)
        else:
            return None

    @property
    def getcontentlength(self):
        if self.catalog.pdf_exists():
            return os.stat(self.catalog.pdf_file()).st_size
        else:
            return 0

    def get_created(self):
        return self.catalog.created

    def get_modified(self):
        return self.catalog.updated

    @property
    def is_collection(self):
        return False

    @property
    def is_object(self):
        return True

    @property
    def exists(self):
        return self.catalog.pdf_exists()

    def read(self):
        if self.catalog.pdf_exists():
            return open(self.catalog.pdf_file(), 'rb')
        else:
            return None

    def delete(self):
        pass

    def copy_object(self, destination, depth=0):
        dest = destination.get_abs_path()
        dest_dir = dirname(dest)
        Path(dest_dir).mkdir(parents=True, exist_ok=True)
        shutil.copy(self.catalog.pdf_file(), dest)

    def move_object(self, destination):
        os.rename(self.get_abs_path(), destination.get_abs_path())

    def write(self, request, temp_file=None, range_start=None):
        autosave_path = url_join(MarkupResource.PATH, 'Autosave', self.path[-1])
        autosave = MarkupResource(path=autosave_path, user=self.user)
        autosave_dir = autosave.get_abs_path()
        log.debug(f'autosave_dir: {autosave_dir}')
        ensure_dir(dirname(autosave_dir))
        return autosave.write(request, temp_file=None, range_start=None)

    def get_acl(self):
        return DavAcl(read=True, write=True, delete=False, full=None)


def get_markup_user_path(user):
    return os.path.join(ASSET_DIR, 'markup', 'webdav', user.username if user else 'unknown_user')


class MarkupResource(MarkupDavResource, DummyFSDAVResource):
    NAME = 'For Markup'
    PATH = f'/{NAME}/'
    SUBDIR = 'markup'

    def __init__(self, path=PATH, user=None):
        super().__init__(path=path, user=user)

    def get_parent(self):
        parent_path = self.path[:-1]
        if len(parent_path):
            return MarkupResource(path=self.construct_path(parent_path, True), user=self.user)
        else:
            return RootFolderResource(user=self.user)

    def get_children(self):
        path = self.get_abs_path()
        if os.path.isdir(path):
            for child in os.listdir(path):
                is_unicode = isinstance(child, str)
                if not is_unicode:
                    child = child.decode(fs_encoding)
                yield MarkupResource(path=url_join(*(self.path + [child])), user=self.user)

    def get_abs_path(self):
        base_dir = os.path.join(get_markup_user_path(self.user), self.SUBDIR)
        ensure_dir(base_dir)
        path = os.path.join(base_dir, *self.path[1:])
        # log.debug(f'markup: get abs path for {self.path}: base {base_dir}')
        return path

    def write(self, request, temp_file=None, range_start=None):
        super().write(request, temp_file=temp_file, range_start=range_start)
        process_markup_pdf.delay(self.get_abs_path(), self.user.username)

    def get_acl(self):
        return FullAcl()


class NonsensicalResource(MarkupDavResource):
    NAME = 'Misc'
    PATH = f'/{NAME}/'
    SUBDIR = 'misc'

    def get_abs_path(self):
        base_dir = os.path.join(get_markup_user_path(self.user), self.SUBDIR)
        ensure_dir(base_dir)
        log.debug(f'nonsense: get abs path for {self.path}: base {base_dir}')
        path = os.path.join(base_dir, *self.path[1:])
        return path

    def get_parent(self):
        log.debug(f'nonsense parent of {self.path} is root')
        return RootFolderResource(path=RootFolderResource.PATH, user=self.user)

    def get_children(self):
        return
        yield

    def delete(self):
        return HttpResponseForbidden()

    def move(self):
        return HttpResponseForbidden()

    def copy(self,  destination, depth=-1):
        return HttpResponseForbidden()

    @property
    def getcontentlength(self):
        return 0

    def get_created(self):
        return datetime.datetime.now()

    def get_modified(self):
        return datetime.datetime.now()

    @property
    def is_collection(self):
        return False

    @property
    def is_object(self):
        return True

    @property
    def exists(self):
        return False
