# This file is part of Radicale - CalDAV and CardDAV server
# Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter
# Copyright © 2008-2017 Guillaume Ayoub
# Copyright © 2017-2023 Unrud <unrud@outlook.com>
# Copyright © 2025-2026 Peter Bieringer <pb@bieringer.de>
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Radicale.  If not, see <http://www.gnu.org/licenses/>.

import posixpath
from http import client
from typing import Union
from urllib.parse import quote

from radicale import httputils, pathutils, storage, types, xmlutils
from radicale.app.base import Access, ApplicationBase
from radicale.log import logger


def propose_filename(collection: storage.BaseCollection, share: Union[dict, None] = None) -> str:
    """Propose a filename for a collection."""
    share_bday_automap = False
    if share and share['Conversion'] == "bday":
        share_bday_automap = True
    if collection.tag == "VADDRESSBOOK" and not share_bday_automap:
        fallback_title = "Address book"
        suffix = ".vcf"
    elif collection.tag == "VCALENDAR" or share_bday_automap:
        fallback_title = "Calendar"
        suffix = ".ics"
    else:
        fallback_title = posixpath.basename(collection.path)
        suffix = ""
    if share and 'Properties' in share and share['Properties'] and "D:displayname" in share['Properties']:
        title = share['Properties']["D:displayname"]
    elif share_bday_automap:
        title = fallback_title
    else:
        title = collection.get_meta("D:displayname") or fallback_title
    if title and not title.lower().endswith(suffix.lower()):
        title += suffix
    return title


class ApplicationPartGet(ApplicationBase):

    def _content_disposition_attachment(self, filename: str) -> str:
        value = "attachment"
        try:
            encoded_filename = quote(filename, encoding=self._encoding)
        except UnicodeEncodeError:
            logger.warning("Failed to encode filename: %r", filename,
                           exc_info=True)
            encoded_filename = ""
        if encoded_filename:
            value += "; filename*=%s''%s" % (self._encoding, encoded_filename)
        return value

    def do_GET(self, environ: types.WSGIEnviron, base_prefix: str, path: str,
               user: str, request_info: dict) -> types.WSGIResponse:
        """Manage GET request."""
        # Redirect to /.web if the root path is requested
        if not pathutils.strip_path(path):
            return httputils.redirect(base_prefix + "/.web")
        if path == "/.web" or path.startswith("/.web/"):
            # Redirect to sanitized path for all subpaths of /.web
            unsafe_path = environ.get("PATH_INFO", "")
            if len(base_prefix) > 0:
                unsafe_path = unsafe_path.removeprefix(base_prefix)
            if unsafe_path != path:
                location = base_prefix + path
                logger.info("Redirecting to sanitized path: %r ==> %r",
                            base_prefix + unsafe_path, location)
                return httputils.redirect(location, client.MOVED_PERMANENTLY)
            # Dispatch /.web path to web module
            return self._web.get(environ, base_prefix, path, user, request_info)
        permissions_filter = None
        share = None
        if self._sharing._enabled:
            # Sharing by token or map (if enabled)
            share = self._sharing.sharing_collection_resolver(path, user)
            if share:
                # overwrite and run through extended permission check
                path = share['PathMapped']
                user = share['Owner']
                permissions_filter = share['Permissions']
        access = Access(self._rights, user, path, permissions_filter)
        if not access.check("r") and "i" not in access.permissions:
            return httputils.NOT_ALLOWED
        with self._storage.acquire_lock("r", user):
            item = next(iter(self._storage.discover(path)), None)
            if not item:
                return httputils.NOT_FOUND
            if access.check("r", item):
                limited_access = False
            elif "i" in access.permissions:
                limited_access = True
            else:
                return httputils.NOT_ALLOWED
            if isinstance(item, storage.BaseCollection):
                if not item.tag:
                    return (httputils.NOT_ALLOWED if limited_access else
                            httputils.DIRECTORY_LISTING)
                if share and share['Conversion'] == "bday":
                    content_type = xmlutils.MIMETYPES["VCALENDAR"]
                else:
                    content_type = xmlutils.MIMETYPES[item.tag]
                content_disposition = self._content_disposition_attachment(
                    propose_filename(item, share))
            elif limited_access:
                return httputils.NOT_ALLOWED
            else:
                if share and share['Conversion'] == "bday":
                    content_type = xmlutils.MIMETYPES["VCALENDAR"]
                else:
                    content_type = xmlutils.OBJECT_MIMETYPES[item.name]
                content_disposition = ""
            assert item.last_modified
            headers = {
                "Content-Type": content_type,
                "Last-Modified": item.last_modified,
                "ETag": item.etag}
            if content_disposition:
                headers["Content-Disposition"] = content_disposition
            if share and share['Conversion'] == "bday":
                if isinstance(item, storage.BaseCollection):
                    # convert VCF to ICS
                    answer = item.serialize(vcf_to_ics=True)
                else:
                    item_converted = item.convert_vcf_to_ics()
                    if item_converted is not None:
                        answer = item_converted.serialize()
                    else:
                        return httputils.NOT_FOUND
            else:
                answer = item.serialize()
            return client.OK, headers, answer, None
