from dataclasses import dataclass
import os
import markdown
import peewee
import typing
import PIL.Image

db = peewee.SqliteDatabase("new.db")
PER_PAGE = 16


def slugify(string):
    def process(char):
        if char in "- \n\t\f":
            return "-"
        elif char.isalpha():
            return char.lower()
        else:
            return ""

    return "".join(process(c) for c in string)[:40]


class Model(peewee.Model):
    class Meta:
        database = db


class Category(Model):
    name = peewee.TextField()
    nicename = peewee.TextField()

    @classmethod
    def all(klass):
        elements = (
            {"url": f"/category/{c.name}", "name": c.nicename} for c in klass.select()
        )
        return {"elements": elements}


class Design(Model):
    visible_id = peewee.IntegerField()
    title = peewee.TextField()
    description = peewee.TextField()
    category = peewee.ForeignKeyField(Category, backref="designs")

    def rendered(self):
        return markdown.markdown(self.description)

    def id_str(self):
        return f"{self.visible_id:05}"

    def slug(self):
        return slugify(self.title)

    def thumbnail(self):
        photo = Photo.select().where(Photo.design == self).limit(1).execute()
        if photo:
            return photo[0].thumb()
        else:
            return None

    def category_list(self):
        categories = Category.select()
        return [{"name": c.name, "selected": "selected='selected'" if c == self.category else ""} for c in categories]

    def to_json(self):
        return {
            "id": self.visible_id,
            "title": self.title,
            "tags": [t.tag_name for t in self.tags],
            "categories": self.category_list(),
            "description": self.description,
            "photos": [{"id": p.id, "image": p.filename} for p in self.photos],
        }
    def update_with(self, stuff):
        # update the category
        if 'title' in stuff:
            self.title = stuff['title']
        if 'category' in stuff:
            self.category = Category.get(name=stuff['category'])
        if 'contents' in stuff:
            self.description = stuff['contents']
        if 'tags' in stuff:
            old_tags = set((t.tag_name for t in self.tags))
            new_tags = set(stuff['tags'])
            for to_add in new_tags - old_tags:
                Tag.create(tag_name=to_add, design=self)
            for to_remove in old_tags - new_tags:
                Tag.get(tag_name=to_remove, design=self).delete_instance()
        self.save()

    @classmethod
    def make_new(klass):
        new_visible_id = klass.select(peewee.fn.max(klass.visible_id)).scalar() + 1
        klass.create(
            visible_id=new_visible_id,
            title="My Great New Design",
            description="A blank canvas awaits...",
            category=Category.get(),
        )
        return new_visible_id

    @classmethod
    def get_all(klass, page=1):
        designs = klass.select().paginate(page, PER_PAGE)
        return Paginated.paginate(page, klass.select().count(), designs,)

    @classmethod
    def get_where(klass, *, tag=None, category=None, page=0):
        query = klass.select(klass)
        if tag is not None:
            query = query.join(Tag).where(Tag.tag_name == tag)
        if category is not None:
            query = query.switch(klass).join(Category).where(Category.name == category)
        query = query.group_by(klass).order_by(klass.id.desc())
        designs = query.paginate(page, PER_PAGE)
        return Paginated.paginate(page, query.count(), designs,)


class Photo(Model):
    filename = peewee.TextField()
    design = peewee.ForeignKeyField(Design, backref="photos", null=True)

    def thumb(self):
        return self.filename[:-4] + "_thumb" + self.filename[-4:]

    @classmethod
    def get_all(klass, page=1):
        size = klass.select().count()
        photos = klass.select().paginate(page, PER_PAGE).order_by(klass.filename.desc())
        return photos, 1, (size // PER_PAGE) + 1

    @classmethod
    def upload(klass, upload, design_id):
        last_image = klass.select().order_by(klass.filename.desc()).limit(1)
        if last_image:
            n = int(last_image[0].filename[:5]) + 1
        else:
            n = 0
        ext = upload.filename[upload.filename.rindex(".")+1:]

        full_name = f"{n:05}.{ext}"
        thumb_name = f"{n:05}_thumb.{ext}"
        img = PIL.Image.open(upload.stream)
        img.thumbnail((400, 400), PIL.Image.ANTIALIAS)
        img.save(os.path.join(os.getcwd(), "static", "photos", full_name))

        img.thumbnail((100, 100), PIL.Image.ANTIALIAS)
        img.save(os.path.join(os.getcwd(), "static", "photos", thumb_name))

        if design_id != -1:
            design = Design.get(visible_id=design_id)
        else:
            design = None
        klass.create(
            filename=full_name,
            design=design,
        )

        return full_name


class Page(Model):
    name = peewee.TextField()
    text = peewee.TextField()
    title = peewee.TextField()

    def rendered(self):
        return markdown.markdown(self.text)


class Tag(Model):
    tag_name = peewee.TextField()
    design = peewee.ForeignKeyField(Design, backref="tags")

    @classmethod
    def all(klass):
        elements = (
            {"url": f"/tag/{t.tag_name}", "name": t.tag_name}
            for t in klass.select(klass.tag_name).distinct().order_by(klass.tag_name)
        )
        return {"elements": elements}


class PageRef(typing.NamedTuple):
    page: int

    def __iter__(self):
        return [self]


@dataclass
class Paginated:
    next_page: typing.Optional[dict]
    prev_page: typing.Optional[dict]
    last_page: int
    contents: typing.List[Design]

    @classmethod
    def paginate(
        cls, offset: int, total: int, contents: typing.List[Design],
    ) -> "Paginated":
        last_page = (total // PER_PAGE) + 1
        next_page = PageRef(page=offset + 1) if offset < last_page else None
        prev_page = PageRef(page=offset - 1) if offset > 1 else None
        return cls(
            next_page=next_page,
            prev_page=prev_page,
            last_page=last_page,
            contents=contents,
        )

MODELS = [
    Category,
    Design,
    Photo,
    Page,
    Tag,
]

# @dataclass
# class Paginated:
#     next_page: typing.Optional[dict]
#     prev_page: typing.Optional[dict]
#     last_page: int
#     contents: typing.List[Design]
