from pathlib import Path
import json
import zipfile
import os
import sys

from .models import Image, Page, BuiltinImages, BuiltinPages, get_tinywiki_default_user
from .enums import WikiContentType, WikiPageStatus
from django.core.files.images import ImageFile
from django.conf import settings as django_settings


def import_builtin_pages(json_file: str | Path, user=None):
    """
    Import builtin pages

    :param json_file: The json file which holds the data.
    :type json_file: str | Path
    :raises ValueError: If file does not exist or json_file is not a file.
    :return: `True` if pages were successfully imported, `False` otherwise
    :rvalue: bool
    """
    if isinstance(json_file, str):
        json_file = Path(json_file).resolve()

    if not json_file.exists():
        raise ValueError("File \"{json_file}\" does not exist!")
    if not json_file.isfile():
        raise ValueError("json_file needs to be a valid file!")

    with open(json_file, "rt", encoding="utf-8") as ifile:
        data = json.loads(ifile.read())

    app = data['app']
    prefix = data['prefix']
    version = data['version']

    page_path = json_file.parent

    try:
        bp = BuiltinPages.objects.get(app=app)
        if version <= bp.version:
            print("Page already imported, skipping!")
            return False

        for slug, spec in data['pages'].items():
            filename = page_path / spec['file']
            with open(filename, "rt", encoding="utf-8") as ifile:
                content = ifile.read()

            try:
                p = Page.objects.get()
                p.title = spec['title']
                p.status_data = WikiPageStatus.from_string(spec['status']).value
                p.content_type_data = WikiContentType.from_string(spec['content_type']).value,
                p.content = content
                p.author = user if user else get_tinywiki_default_user()
                p.save()
            except Page.DoesNotExist:
                Page.objects.create(slug=slug,
                                    title=spec['title'],
                                    status_data=WikiPageStatus.from_string(spec['status']).value,
                                    content_type_data=WikiContentType.from_string(spec['content_type']).value,
                                    content=content,
                                    author=user if user else get_tinywiki_default_user())
        bp.prefix = prefix
        bp.version = version
        bp.save()
        return True
    except BuiltinPages.DoesNotExist:
        for slug, spec in data['pages'].items():
            filename = page_path / spec['file']
            with open(filename, "rt", encoding="utf-8") as ifile:
                content = ifile.read()

            Page.objects.create(slug=slug,
                                title=spec['title'],
                                status_data=WikiPageStatus.from_string(spec['status']).value,
                                content_type_data=WikiContentType.from_string(spec['content_type']).value,
                                content=content,
                                author=user if user else get_tinywiki_default_user())
        BuiltinPages.objects.create(app=app, prefix=prefix, version=version)
        return True


def import_builtin_pages_from_zip(zip: str | Path, user=None):
    """
    Import builtin pages

    :param zip: The zip file which holds the data.
    :type zip: str | Path
    :raises ValueError: If file does not exist or json_file is not a file.
    :return: `True` if pages were successfully imported, `False` otherwise
    :rvalue: bool
    """
    if isinstance(zip, str):
        zip = Path(zip).resolve()

    if not zip.exists():
        raise ValueError(f"File \"{zip}\" does not exist!")
    if not zip.is_file():
        raise ValueError("zip needs to be a valid file!")
    if not zipfile.is_zipfile(zip):
        raise ValueError("zip needs to be a zip file!")


    with zipfile.ZipFile(zip, "r") as zf:
        with zf.open('pages.json') as json_file:
            data = json.loads(json_file.read().decode('utf-8'))

        app = data['app']
        prefix = data['prefix']
        version = data['version']

        try:
            bp = BuiltinPages.objects.get(app=app)
            if version <= bp.version:
                print("Page already imported, skipping!")
                return False

            for slug, spec in data['pages'].items():
                with zf.open(spec['file']) as ifile:
                    content = ifile.read().decode('utf-8')

                try:
                    p = Page.objects.get()
                    p.title = spec['title']
                    p.status_data = WikiPageStatus.from_string(spec['status']).value
                    p.content_type_data = WikiContentType.from_string(spec['content_type']).value,
                    p.content = content
                    p.author = user if user else get_tinywiki_default_user()
                    p.save()
                except Page.DoesNotExist:
                    Page.objects.create(slug=slug,
                                        title=spec['title'],
                                        status_data=WikiPageStatus.from_string(spec['status']).value,
                                        content_type_data=WikiContentType.from_string(spec['content_type']).value,
                                        content=content,
                                        author=user if user else get_tinywiki_default_user())
            bp.prefix = prefix
            bp.version = version
            bp.save()
            return True
        except BuiltinPages.DoesNotExist:
            for slug, spec in data['pages'].items():
                with zf.open(spec['file']) as ifile:
                    content = ifile.read().decode("utf-8")

                Page.objects.create(slug=slug,
                                    title=spec['title'],
                                    status_data=WikiPageStatus.from_string(spec['status']).value,
                                    content_type_data=WikiContentType.from_string(spec['content_type']).value,
                                    content=content,
                                    author=user if user else get_tinywiki_default_user())
            BuiltinPages.objects.create(app=app, prefix=prefix, version=version)
            return True


def import_builtin_images(json_file: str | Path, user=None):
    if isinstance(json_file, str):
        json_file = Path(json_file).resolve()
    if not json_file.exists():
        raise ValueError(f"File \"{json_file}\" does not exist!")
    if not json_file.isfile():
        raise ValueError("json_file needs to be a valid file!")

    image_dir = json_file.parent

    with open(json_file, "rt", encoding="utf-8") as ifile:
        data = json.loads(ifile.read())

    version = data['version']
    app = data['app']
    prefix = data['prefix']

    try:
        bi = BuiltinImages.objects.get(app=app)
        if bi.version <= data['version']:
            return False

        for slug, spec in data['images'].items():
            try:
                img = Image.objects.get(slug=spec['slug'])
                continue
            except Image.DoesNotExist:
                image_basename = spec['image']
                image_filename = image_dir / spec.pop("image")
                spec['slug'] = slug
                spec['user'] = user if user else get_tinywiki_default_user()

                img = Image(**spec)
                with open(image_filename, "rb") as ifile:
                    img_file = ImageFile(ifile, image_basename)
                    img.image.save(image_basename, img_file)
                img.save()
        bi.version = version
        bi.save()
        return True
    except BuiltinImages.DoesNotExist:
        for slug, spec in data['images'].items():
            spec = spec
            image_basename = spec['image']
            image_filename = image_dir / spec.pop("image")
            spec['slug'] = slug
            spec['user'] = user if user else get_tinywiki_default_user()

            img = Image(**spec)
            with open(image_filename, "rb") as ifile:
                img_file = ImageFile(ifile, image_basename)
                img.image.save(image_basename, img_file)
            img.save()

        BuiltinImages.objects.create(app=app, prefix=prefix, version=version)
        return True


def import_builtin_images_from_zip(zip_file: str | Path, user=None):
    if not isinstance(zip_file, Path):
        zip_file = Path(zip_file).resolve()

    if not zip_file.exists():
        raise ValueError(f"File \"{zip_file}\" does not exist!")
    if not zip_file.isfile():
        raise ValueError("zip needs to be a valid file!")
    if not zip_file.is_zipfile(zip_file):
        raise ValueError("zip needs to be a zip file!")

    with zipfile.ZipFile(zip_file, "r", encoding="utf-8") as zf:
        data = json.loads(zf.open('images.json').read().decode('utf-8'))

        version = data['version']
        app = data['app']
        prefix = data['prefix']

        try:
            bi = BuiltinImages.objects.get(app=app)
            if bi.version <= data['version']:
                return False

            for slug, spec in data['images'].items():
                try:
                    img = Image.objects.get(slug=spec['slug'])
                    continue
                except Image.DoesNotExist:
                    image_basename = os.path.basename(spec['image'])
                    image_filename = spec.pop("image")
                    spec['slug'] = slug
                    spec['user'] = user if user else get_tinywiki_default_user()

                    img = Image(**spec)
                    with zf.open(image_filename) as ifile:
                        img_file = ImageFile(ifile, image_basename)
                        img.image.save(image_basename, img_file)
                    img.save()
            bi.version = version
            bi.save()
            return True
        except BuiltinImages.DoesNotExist:
            for slug, spec in data['images'].items():
                spec = spec
                image_basename = os.path.basename(spec['image'])
                image_filename = spec.pop("image")
                spec['slug'] = slug
                spec['user'] = user if user else get_tinywiki_default_user()

                img = Image(**spec)
                with zf.open(image_filename) as ifile:
                    img_file = ImageFile(ifile, image_basename)
                    img.image.save(image_basename, img_file)
                img.save()

            BuiltinImages.objects.create(app=app, prefix=prefix, version=version)
            return True


def export_wiki_content(app: str,
                        filename,
                        prefix: str | None = None,
                        page_version: int = 0,
                        image_version: int = 0) -> bool:
    prefix = prefix
    try:
        bp = BuiltinPages.objects.get(app=app)
        if page_version < bp.version:
            page_version = bp.version + 1
        if not prefix:
            prefix = bp.prefix
    except BuiltinPages.DoesNotExist:
        bp = None

    try:
        bi = BuiltinImages.objects.get(app=app)
        if image_version < bi.version:
            image_version = bi.version + 1
        if not prefix:
            prefix = bp.prefix
    except BuiltinImages.DoesNotExist:
        bi = None

    if not prefix:
        raise RuntimeError("No slug prefix! Can not export!")

    pages = Page.objects.filter(slug__startswith=prefix)
    images = Image.objects.filter(slug__startswith=prefix)

    if not pages and not images:
        return False

    with zipfile.ZipFile(filename, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
        if pages:

            pages_data = {
                'app': app,
                'prefix': prefix,
                'version': page_version,
                'pages': {}
            }

            for p in pages:
                if WikiContentType.BBCODE == p.content_type:
                    extension = 'bbcode'
                else:
                    extension = 'md'
                page_file = f"pages/{p.slug}.{extension}"
                pages_data['pages'][p.slug] = {
                    'title': p.title,
                    'content_type': p.content_type.value,
                    'status': p.status.value,
                    'file': page_file,
                }
                zf.writestr(page_file, p.content)
            zf.writestr('pages.json', json.dumps(pages_data, ensure_ascii=False, indent=4))
            if bp:
                bp.version = page_version
                bp.save()
            else:
                BuiltinPages.objects.create(app=app, prefix=prefix, version=page_version)

        if images:
            images_data = {
                'app': app,
                'prefix': prefix,
                'version': image_version,
                'images': {}
            }
            for i in images:
                if not os.path.isfile(i.image.path):
                    continue

                arcname = f"images/{os.path.basename(i.image.path)}"
                images_data['images'][i.slug] = {
                    'slug': i.slug,
                    'alt': i.alt,
                    'file': arcname,
                    'description': i.description,
                }
                zf.write(i.image.path, arcname)
            zf.writestr('images.json', json.dumps(images_data, ensure_ascii=False, indent=4))
            if bi:
                bi.version = image_version
                bi.save()
            else:
                BuiltinImages.objects.create(app=app, prefix=prefix, version=image_version)
    return True


def export_tinywiki_wiki_content(filename=None):
    if filename is None:
        filename = Path(django_settings.MEDIA_ROOT) / "tinywiki-tinywiki.zip"
    export_wiki_content('tinywiki', filename, 'tw-')
