import pickle
from typing import Optional, Dict, Any, List, cast
from enum import Enum

import orjson

from fastapi import APIRouter, Depends, Query
from pydantic import validator
from sqlmodel import Session, Field, select  # type: ignore[import]
from sqlalchemy import distinct  # type: ignore[import]


from app.db import get_db, FeedModel, FeedBase

router = APIRouter()


class FeedRead(FeedBase):
    # parse from the backend to data structures
    tags: List[str] = Field(default_factory=list)
    data: Dict[str, Any] = Field(default_factory={})

    @validator("tags", pre=True)
    def parse_tags(cls, v: Any) -> List[str]:
        return cast(List[str], orjson.loads(v))

    @validator("data", pre=True)
    def parse_data(cls, v: Any) -> Dict[str, Any]:
        if v is None:
            return {}
        else:
            return cast(Dict[str, Any], pickle.loads(v))


class OrderBy(Enum):
    score = "score"
    when = "when"


class Sort(Enum):
    asc = "asc"
    ascending = "ascending"
    desc = "desc"
    descending = "descending"


# items which shouldn't be shown when sorted by 'score'
# since it'd make the feed too busy
INDIVIDUAL_FEED_TYPES = [
    "anime_episode",
    "manga_chapter",
    "listen",
    "trakt_history_episode",
    "trakt_history_movie",
]


@router.get("/types", response_model=List[str])
async def data_types(
    session: Session = Depends(get_db),
) -> List[str]:
    stmt = select(distinct(FeedModel.ftype))
    with session:
        items: List[str] = list(session.exec(stmt))
    return items


@router.get("/", response_model=List[FeedRead])
async def data(
    offset: int = 0,
    limit: int = Query(default=100, lte=100),
    order_by: OrderBy = Query(default=OrderBy.when),
    sort: Sort = Query(default=Sort.desc),
    ftype: Optional[str] = Query(default=None, min_length=2),
    query: Optional[str] = Query(default=None, min_length=2),
    title: Optional[str] = Query(default=None, min_length=2),
    creator: Optional[str] = Query(default=None, min_length=2),
    subtitle: Optional[str] = Query(default=None, min_length=2),
    session: Session = Depends(get_db),
) -> List[FeedRead]:
    stmt = select(FeedModel)
    if ftype is not None and ftype.strip():
        if parts := ftype.strip().split(","):
            stmt = stmt.filter(FeedModel.ftype.in_(parts))  # type: ignore

    if query is None:
        if title:
            stmt = stmt.filter(FeedModel.title.ilike(f"%{title}%"))  # type: ignore
        if creator:
            stmt = stmt.filter(FeedModel.creator.ilike(f"%{creator}%"))  # type: ignore
        if subtitle:
            stmt = stmt.filter(FeedModel.subtitle.ilike(f"%{subtitle}%"))  # type: ignore
    else:
        stmt = stmt.filter(
            (FeedModel.title.ilike(f"%{query}%"))  # type: ignore
            | (FeedModel.creator.ilike(f"%{query}%"))  # type: ignore
            | (FeedModel.subtitle.ilike(f"%{query}%"))  # type: ignore
            | (FeedModel.model_id.ilike(f"%{query}%"))  # type: ignore
        )
    if order_by == OrderBy.score:
        stmt = stmt.filter(FeedModel.score != None)
        stmt = stmt.filter(FeedModel.ftype.notin_(INDIVIDUAL_FEED_TYPES))  # type: ignore
        # ORDER BY Score [CHOSEN], When DESC to show things I completed recently higher when sorting by score
        stmt = stmt.order_by(FeedModel.score.asc() if sort == Sort.asc else FeedModel.score.desc(), FeedModel.when.desc())  # type: ignore
    else:
        stmt = stmt.order_by(FeedModel.when.asc() if sort == Sort.asc else FeedModel.when.desc())  # type: ignore
    stmt = stmt.limit(limit).offset(offset)
    with session:
        items: List[FeedModel] = list(session.exec(stmt))
    return items

app.db.FeedModel.score.asc sqlmodel.select pickle.loads sqlalchemy.distinct orjson.loads app.db.FeedModel.score.desc app.db.FeedModel.when.desc app.db.FeedModel.model_id.ilike pydantic.validator app.db.FeedModel.creator.ilike app.db.FeedModel.ftype.in_ fastapi.Query app.db.FeedModel.subtitle.ilike fastapi.Depends app.db.FeedModel.ftype.notin_ app.db.FeedModel.when.asc sqlmodel.Field fastapi.APIRouter app.db.FeedModel.title.ilike
[((363, 374), 'fastapi.APIRouter', 'APIRouter', ([], {}), '()\n', (372, 374), False, 'from fastapi import APIRouter, Depends, Query\n'), ((473, 500), 'sqlmodel.Field', 'Field', ([], {'default_factory': 'list'}), '(default_factory=list)\n', (478, 500), False, 'from sqlmodel import Session, Field, select\n'), ((528, 553), 'sqlmodel.Field', 'Field', ([], {'default_factory': '{}'}), '(default_factory={})\n', (533, 553), False, 'from sqlmodel import Session, Field, select\n'), ((560, 587), 'pydantic.validator', 'validator', (['"""tags"""'], {'pre': '(True)'}), "('tags', pre=True)\n", (569, 587), False, 'from pydantic import validator\n'), ((688, 715), 'pydantic.validator', 'validator', (['"""data"""'], {'pre': '(True)'}), "('data', pre=True)\n", (697, 715), False, 'from pydantic import validator\n'), ((1384, 1399), 'fastapi.Depends', 'Depends', (['get_db'], {}), '(get_db)\n', (1391, 1399), False, 'from fastapi import APIRouter, Depends, Query\n'), ((1653, 1680), 'fastapi.Query', 'Query', ([], {'default': '(100)', 'lte': '(100)'}), '(default=100, lte=100)\n', (1658, 1680), False, 'from fastapi import APIRouter, Depends, Query\n'), ((1706, 1733), 'fastapi.Query', 'Query', ([], {'default': 'OrderBy.when'}), '(default=OrderBy.when)\n', (1711, 1733), False, 'from fastapi import APIRouter, Depends, Query\n'), ((1752, 1776), 'fastapi.Query', 'Query', ([], {'default': 'Sort.desc'}), '(default=Sort.desc)\n', (1757, 1776), False, 'from fastapi import APIRouter, Depends, Query\n'), ((1805, 1838), 'fastapi.Query', 'Query', ([], {'default': 'None', 'min_length': '(2)'}), '(default=None, min_length=2)\n', (1810, 1838), False, 'from fastapi import APIRouter, Depends, Query\n'), ((1867, 1900), 'fastapi.Query', 'Query', ([], {'default': 'None', 'min_length': '(2)'}), '(default=None, min_length=2)\n', (1872, 1900), False, 'from fastapi import APIRouter, Depends, Query\n'), ((1929, 1962), 'fastapi.Query', 'Query', ([], {'default': 'None', 'min_length': '(2)'}), '(default=None, min_length=2)\n', (1934, 1962), False, 'from fastapi import APIRouter, Depends, Query\n'), ((1993, 2026), 'fastapi.Query', 'Query', ([], {'default': 'None', 'min_length': '(2)'}), '(default=None, min_length=2)\n', (1998, 2026), False, 'from fastapi import APIRouter, Depends, Query\n'), ((2058, 2091), 'fastapi.Query', 'Query', ([], {'default': 'None', 'min_length': '(2)'}), '(default=None, min_length=2)\n', (2063, 2091), False, 'from fastapi import APIRouter, Depends, Query\n'), ((2116, 2131), 'fastapi.Depends', 'Depends', (['get_db'], {}), '(get_db)\n', (2123, 2131), False, 'from fastapi import APIRouter, Depends, Query\n'), ((2165, 2182), 'sqlmodel.select', 'select', (['FeedModel'], {}), '(FeedModel)\n', (2171, 2182), False, 'from sqlmodel import Session, Field, select\n'), ((1435, 1460), 'sqlalchemy.distinct', 'distinct', (['FeedModel.ftype'], {}), '(FeedModel.ftype)\n', (1443, 1460), False, 'from sqlalchemy import distinct\n'), ((665, 680), 'orjson.loads', 'orjson.loads', (['v'], {}), '(v)\n', (677, 680), False, 'import orjson\n'), ((3131, 3176), 'app.db.FeedModel.ftype.notin_', 'FeedModel.ftype.notin_', (['INDIVIDUAL_FEED_TYPES'], {}), '(INDIVIDUAL_FEED_TYPES)\n', (3153, 3176), False, 'from app.db import get_db, FeedModel, FeedBase\n'), ((3404, 3425), 'app.db.FeedModel.when.desc', 'FeedModel.when.desc', ([], {}), '()\n', (3423, 3425), False, 'from app.db import get_db, FeedModel, FeedBase\n'), ((865, 880), 'pickle.loads', 'pickle.loads', (['v'], {}), '(v)\n', (877, 880), False, 'import pickle\n'), ((2304, 2330), 'app.db.FeedModel.ftype.in_', 'FeedModel.ftype.in_', (['parts'], {}), '(parts)\n', (2323, 2330), False, 'from app.db import get_db, FeedModel, FeedBase\n'), ((2420, 2455), 'app.db.FeedModel.title.ilike', 'FeedModel.title.ilike', (['f"""%{title}%"""'], {}), "(f'%{title}%')\n", (2441, 2455), False, 'from app.db import get_db, FeedModel, FeedBase\n'), ((2524, 2563), 'app.db.FeedModel.creator.ilike', 'FeedModel.creator.ilike', (['f"""%{creator}%"""'], {}), "(f'%{creator}%')\n", (2547, 2563), False, 'from app.db import get_db, FeedModel, FeedBase\n'), ((2633, 2674), 'app.db.FeedModel.subtitle.ilike', 'FeedModel.subtitle.ilike', (['f"""%{subtitle}%"""'], {}), "(f'%{subtitle}%')\n", (2657, 2674), False, 'from app.db import get_db, FeedModel, FeedBase\n'), ((2952, 2990), 'app.db.FeedModel.model_id.ilike', 'FeedModel.model_id.ilike', (['f"""%{query}%"""'], {}), "(f'%{query}%')\n", (2976, 2990), False, 'from app.db import get_db, FeedModel, FeedBase\n'), ((3333, 3354), 'app.db.FeedModel.score.asc', 'FeedModel.score.asc', ([], {}), '()\n', (3352, 3354), False, 'from app.db import get_db, FeedModel, FeedBase\n'), ((3380, 3402), 'app.db.FeedModel.score.desc', 'FeedModel.score.desc', ([], {}), '()\n', (3400, 3402), False, 'from app.db import get_db, FeedModel, FeedBase\n'), ((3482, 3502), 'app.db.FeedModel.when.asc', 'FeedModel.when.asc', ([], {}), '()\n', (3500, 3502), False, 'from app.db import get_db, FeedModel, FeedBase\n'), ((3528, 3549), 'app.db.FeedModel.when.desc', 'FeedModel.when.desc', ([], {}), '()\n', (3547, 3549), False, 'from app.db import get_db, FeedModel, FeedBase\n'), ((2881, 2919), 'app.db.FeedModel.subtitle.ilike', 'FeedModel.subtitle.ilike', (['f"""%{query}%"""'], {}), "(f'%{query}%')\n", (2905, 2919), False, 'from app.db import get_db, FeedModel, FeedBase\n'), ((2743, 2778), 'app.db.FeedModel.title.ilike', 'FeedModel.title.ilike', (['f"""%{query}%"""'], {}), "(f'%{query}%')\n", (2764, 2778), False, 'from app.db import get_db, FeedModel, FeedBase\n'), ((2811, 2848), 'app.db.FeedModel.creator.ilike', 'FeedModel.creator.ilike', (['f"""%{query}%"""'], {}), "(f'%{query}%')\n", (2834, 2848), False, 'from app.db import get_db, FeedModel, FeedBase\n')]