import mimetypes
import os
import urllib.parse
from datetime import datetime
from pathlib import Path
from urllib.parse import quote, unquote

from aiohttp import web

from snek.system.view import BaseView


class DriveView(BaseView):

    async def get(self):
        target = await self.services.user.get_home_folder(self.session.get("uid"))
        rel_path = self.request.match_info.get("rel_path", "")
        if rel_path:
            target = target.joinpath(rel_path)

        if not target.exists():
            return web.HTTPNotFound(reason="Path not found")

        if target.is_dir():
            return await self.render_template("drive.html", {"path": rel_path})
        if target.is_file():
            return web.FileResponse(target)


class DriveApiView(BaseView):
    async def get(self):
        target = await self.services.user.get_home_folder(self.session.get("uid"))
        rel = self.request.query.get("path", "")
        offset = int(self.request.query.get("offset", 0))
        limit = int(self.request.query.get("limit", 20))

        if rel:
            target = target.joinpath(rel)

        if not target.exists():
            return web.json_response({"error": "Not found"}, status=404)

        if target.is_dir():
            entries = []
            for p in sorted(
                target.iterdir(), key=lambda p: (p.is_file(), p.name.lower())
            ):
                item_path = (Path(rel) / p.name).as_posix()
                mime = (
                    mimetypes.guess_type(p.name)[0]
                    if p.is_file()
                    else "inode/directory"
                )
                url = (
                    self.request.url.with_path(
                        f"/drive/{urllib.parse.quote(item_path)}"
                    )
                    if p.is_file()
                    else None
                )
                entries.append(
                    {
                        "name": p.name,
                        "type": "directory" if p.is_dir() else "file",
                        "mimetype": mime,
                        "size": p.stat().st_size if p.is_file() else None,
                        "path": item_path,
                        "url": url,
                    }
                )
            import json

            total = len(entries)
            items = entries[offset : offset + limit]
            return web.json_response(
                {
                    "items": json.loads(json.dumps(items, default=str)),
                    "pagination": {"offset": offset, "limit": limit, "total": total},
                }
            )

        url = self.request.url.with_path(f"/drive/{urllib.parse.quote(rel)}")
        return web.json_response(
            {
                "name": target.name,
                "type": "file",
                "mimetype": mimetypes.guess_type(target.name)[0],
                "size": target.stat().st_size,
                "path": rel,
                "url": str(url),
            }
        )


class DriveView222(BaseView):
    PAGE_SIZE = 20

    async def base_path(self):
        return await self.services.user.get_home_folder(self.session.get("uid"))

    async def get_full_path(self, rel_path):
        base_path = await self.base_path()
        safe_path = os.path.normpath(unquote(rel_path or ""))
        full_path = os.path.abspath(os.path.join(base_path, safe_path))
        if not full_path.startswith(os.path.abspath(base_path)):
            raise web.HTTPForbidden(reason="Invalid path")
        return full_path

    async def make_absolute_url(self, rel_path):
        rel_path = rel_path.lstrip("/")
        url = str(self.request.url.with_path(f"/drive/{quote(rel_path)}"))
        return url

    async def entry_details(self, dir_path, entry, parent_rel_path):
        entry_path = os.path.join(dir_path, entry)
        stat = os.stat(entry_path)
        is_dir = os.path.isdir(entry_path)
        mimetype = (
            None
            if is_dir
            else (mimetypes.guess_type(entry_path)[0] or "application/octet-stream")
        )
        size = stat.st_size if not is_dir else None
        created_at = datetime.fromtimestamp(stat.st_ctime).isoformat()
        updated_at = datetime.fromtimestamp(stat.st_mtime).isoformat()
        rel_entry_path = os.path.join(parent_rel_path, entry).replace("\\", "/")
        return {
            "name": entry,
            "type": "dir" if is_dir else "file",
            "mimetype": mimetype,
            "size": size,
            "created_at": created_at,
            "updated_at": updated_at,
            "absolute_url": await self.make_absolute_url(rel_entry_path),
        }

    async def get(self):
        rel_path = self.request.match_info.get("rel_path", "")
        full_path = await self.get_full_path(rel_path)
        page = int(self.request.query.get("page", 1))
        page_size = int(self.request.query.get("page_size", self.PAGE_SIZE))
        abs_url = await self.make_absolute_url(rel_path)

        if not os.path.exists(full_path):
            raise web.HTTPNotFound(reason="Path not found")

        if os.path.isdir(full_path):
            entries = os.listdir(full_path)
            entries.sort()
            start = (page - 1) * page_size
            end = start + page_size
            paged_entries = entries[start:end]
            details = [
                await self.entry_details(full_path, entry, rel_path)
                for entry in paged_entries
            ]
            return web.json_response(
                {
                    "path": rel_path,
                    "absolute_url": abs_url,
                    "entries": details,
                    "total": len(entries),
                    "page": page,
                    "page_size": page_size,
                }
            )
        else:
            with open(full_path, "rb") as f:
                content = f.read()
            mimetype = mimetypes.guess_type(full_path)[0] or "application/octet-stream"
            headers = {"X-Absolute-Url": abs_url}
            return web.Response(body=content, content_type=mimetype, headers=headers)

    async def post(self):
        rel_path = self.request.match_info.get("rel_path", "")
        full_path = await self.get_full_path(rel_path)
        abs_url = await self.make_absolute_url(rel_path)
        if os.path.exists(full_path):
            raise web.HTTPConflict(reason="File or directory already exists")
        data = await self.request.post()
        if data.get("type") == "dir":
            os.makedirs(full_path)
            return web.json_response(
                {"status": "created", "type": "dir", "absolute_url": abs_url}
            )
        else:
            file_field = data.get("file")
            if not file_field:
                raise web.HTTPBadRequest(reason="No file uploaded")
            with open(full_path, "wb") as f:
                f.write(file_field.file.read())
            return web.json_response(
                {"status": "created", "type": "file", "absolute_url": abs_url}
            )

    async def put(self):
        rel_path = self.request.match_info.get("rel_path", "")
        full_path = await self.get_full_path(rel_path)
        abs_url = await self.make_absolute_url(rel_path)
        if not os.path.exists(full_path):
            raise web.HTTPNotFound(reason="File not found")
        if os.path.isdir(full_path):
            raise web.HTTPBadRequest(reason="Cannot overwrite directory")
        body = await self.request.read()
        with open(full_path, "wb") as f:
            f.write(body)
        return web.json_response({"status": "updated", "absolute_url": abs_url})

    async def delete(self):
        rel_path = self.request.match_info.get("rel_path", "")
        full_path = await self.get_full_path(rel_path)
        abs_url = await self.make_absolute_url(rel_path)
        if not os.path.exists(full_path):
            raise web.HTTPNotFound(reason="Path not found")
        if os.path.isdir(full_path):
            os.rmdir(full_path)
            return web.json_response(
                {"status": "deleted", "type": "dir", "absolute_url": abs_url}
            )
        else:
            os.remove(full_path)
            return web.json_response(
                {"status": "deleted", "type": "file", "absolute_url": abs_url}
            )


class DriveViewi2(BaseView):

    login_required = True

    async def get(self):

        drive_uid = self.request.match_info.get("drive")

        before = self.request.query.get("before")
        filters = {}
        if before:
            filters["created_at__lt"] = before

        if drive_uid:
            filters["drive_uid"] = drive_uid
            drive = await self.services.drive.get(uid=drive_uid)
            drive_items = []

            async for item in self.services.drive_item.find(**filters):
                record = item.record
                record["url"] = "/drive.bin/" + record["uid"] + "." + item.extension
                drive_items.append(record)
            return web.json_response(drive_items)

        user = await self.services.user.get(uid=self.session.get("uid"))

        drives = []
        async for drive in self.services.drive.get_by_user(user["uid"]):
            record = drive.record
            record["items"] = []
            async for item in drive.items:
                drive_item_record = item.record
                drive_item_record["url"] = (
                    "/drive.bin/" + drive_item_record["uid"] + "." + item.extension
                )
                record["items"].append(item.record)
            drives.append(record)

        return web.json_response(drives)
