from flask import Blueprint, Response, abort, flash, g, make_response, redirect, render_template, request, session, url_for

from src.lib.announcements import get_artist_announcements
from src.lib.artist import (
    delete_creator_link,
    get_artist,
    get_artists_by_update_time,
    get_fancards_by_artist,
    get_top_artists_by_faves,
    create_unapproved_link_request,
    get_linked_creators,
)
from src.lib.dms import count_user_dms, get_artist_dms
from src.lib.filehaus import get_artist_share_count, get_artist_shares
from src.lib.post import (
    get_all_posts_by_artist,
    get_artist_post_count,
    get_artist_posts_summary,
    get_fileserver_for_value,
    get_render_data_for_posts,
)
from src.lib.posts import count_all_posts_for_tag, get_all_tags, get_tagged_posts
from src.pages.artists_types import (
    ArtistAnnouncementsProps,
    ArtistDisplayData,
    ArtistDMsProps,
    ArtistFancardsProps,
    ArtistPageProps,
    ArtistShareProps,
    LinkedAccountsProps,
)
from src.types.paysites import Paysite, Paysites
from src.utils.utils import offset_list, parse_int, positive_or_none, sort_dict_list_by, step_int, take
from src.types.account.account import Account
from src.utils.decorators import require_login

artists_bp = Blueprint("artists", __name__)


@artists_bp.route("/artists")
def list():
    base = dict()
    limit = 50

    results = get_top_artists_by_faves(0, limit)
    props = dict(
        currentPage="artists",
        display="cached popular artists",
        count=len(results),
        limit=limit,
    )

    response = make_response(render_template("artists.html", props=props, results=results, base=base), 200)
    response.headers["Cache-Control"] = "s-maxage=60"
    return response


@artists_bp.route("/artists/updated")
def updated():
    base = dict(commit=True, sort_by="updated")
    limit = 50

    results = get_artists_by_update_time(offset=0, limit=limit)
    props = dict(
        currentPage="artists",
        display="cached updated artists",
        count=len(results),
        limit=limit,
    )

    response = make_response(render_template("artists.html", props=props, results=results, base=base), 200)
    response.headers["Cache-Control"] = "max-age=60, public, stale-while-revalidate=2592000"
    return response


@artists_bp.route("/<service>/user/<artist_id>")
def get(service: str, artist_id: str):
    if service == "discord":
        response = redirect(f"/discord/server/{artist_id}", 308)
        response.headers["Cache-Control"] = "s-maxage=86400"
        return response

    base = request.args.to_dict()
    base.pop("o", None)
    base["service"] = service
    base["artist_id"] = artist_id

    artist = get_artist(service, artist_id)
    if artist is None:
        return redirect(url_for("artists.list"))
    elif artist["public_id"] == artist_id and artist["id"] != artist_id:
        return redirect(url_for("artists.get", service=service, artist_id=artist["id"]))

    query = request.args.get("q", default="").strip()
    tags = sorted(request.args.getlist("tag"))
    limit = 50
    offset = positive_or_none(step_int(parse_int(request.args.get("o"), 0), limit))
    if offset is None:
        return redirect(url_for("artists.list"))

    if tags:
        posts = get_tagged_posts(tags, offset, limit, service, artist_id)
        total_count = count_all_posts_for_tag(tags, service, artist_id)
    elif not query or len(query) < 2:
        total_count = get_artist_post_count(service, artist_id)
        if offset > total_count:
            return redirect(url_for("artists.get", service=service, artist_id=artist_id))
        else:
            posts = get_artist_posts_summary(artist_id, service, offset, limit, "published DESC NULLS LAST")
    else:
        (posts, total_count) = do_artist_post_search(artist_id, service, query, offset, limit)

    (
        result_previews,
        result_attachments,
        result_is_image,
    ) = get_render_data_for_posts(posts)

    props = ArtistPageProps(
        id=artist_id,
        service=service,
        session=session,
        name=artist["name"],
        count=total_count,
        limit=limit,
        artist=artist,
        display_data=make_artist_display_data(artist),
        dm_count=count_user_dms(service, artist_id),
        share_count=get_artist_share_count(service, artist_id),
        has_links="✔️" if artist["relation_id"] else "0",
    )

    response = make_response(
        render_template(
            "user.html",
            props=props,
            base=base,
            results=posts,
            result_previews=result_previews,
            result_attachments=result_attachments,
            result_is_image=result_is_image,
        ),
        200,
    )
    response.headers["Cache-Control"] = "s-maxage=60"
    return response


@artists_bp.get("/<service>/user/<artist_id>/tags")
def get_tags(service: str, artist_id: str):
    artist = get_artist(service, artist_id)
    if not artist:
        response = redirect(url_for("artists.list"), code=301)
        response.headers["Cache-Control"] = "s-maxage=60"
        return response
    elif artist["public_id"] == artist_id and artist["id"] != artist_id:
        return redirect(url_for("artists.get_tags", service=service, artist_id=artist["id"]), code=301)

    tags = get_all_tags(service, artist_id)

    response = make_response(
        render_template(
            "artist/tags.html",
            props={
                "display_data": make_artist_display_data(artist),
                "artist": artist,
                "service": service,
                "id": artist["id"],
                "share_count": get_artist_share_count(service, artist_id),
                "dm_count": count_user_dms(service, artist_id),
                "has_links": "✔️" if artist["relation_id"] else "0",
            },
            tags=tags,
            service=service,
            artist=artist,
        )
    )
    response.headers["Cache-Control"] = "s-maxage=600"
    return response


@artists_bp.route("/fanbox/user/<artist_id>/fancards")
def get_fancards(artist_id: str):
    service = "fanbox"
    artist = get_artist(service, artist_id)
    if not artist:
        response = redirect(url_for("artists.list"), code=301)
        response.headers["Cache-Control"] = "s-maxage=60"
        return response
    elif artist["public_id"] == artist_id:
        return redirect(url_for("artists.get_fancards", artist_id=artist["id"]), code=301)

    fancards = get_fancards_by_artist(artist_id)
    for fancard in fancards:
        fhash = fancard["hash"]
        ext = fancard["ext"]
        fancard["path"] = f"/data/{fhash[0:2]}/{fhash[2:4]}/{fhash}{ext}"
        fancard["server"] = get_fileserver_for_value(fancard["path"])

    props = ArtistFancardsProps(
        id=artist_id,
        session=session,
        artist=artist,
        display_data=make_artist_display_data(artist),
        fancards=fancards,
        share_count=get_artist_share_count(artist_id=artist_id, service=service),
        dm_count=count_user_dms(service, artist_id),
        has_links="✔️" if artist["relation_id"] else "0",
    )

    response = make_response(
        render_template(
            "artist/fancards.html",
            artist=artist,
            fancards=fancards,
            props=props,
        ),
        200,
    )
    response.headers["Cache-Control"] = "s-maxage=60"
    return response


@artists_bp.route("/<service>/user/<artist_id>/shares")
def get_shares(service: str, artist_id: str):
    base = request.args.to_dict()
    base.pop("o", None)
    base["service"] = service
    base["artist_id"] = artist_id

    dm_count = count_user_dms(service, artist_id)
    shares = get_artist_shares(artist_id, service)

    artist = get_artist(service, artist_id)
    if artist is None:
        return redirect(url_for("artists.list"))
    elif artist["public_id"] == artist_id and artist["id"] != artist_id:
        return redirect(url_for("artists.get_shares", service=service, artist_id=artist["id"]), code=301)

    props = ArtistShareProps(
        display_data=make_artist_display_data(artist),
        service=service,
        session=session,
        artist=artist,
        id=artist_id,
        dm_count=dm_count,
        share_count=len(shares),
        has_links="✔️" if artist["relation_id"] else "0",
    )

    response = make_response(
        render_template("artist/shares.html", results=shares, props=props, base=base),
        200,
    )
    response.headers["Cache-Control"] = "s-maxage=60"
    return response


@artists_bp.route("/<service>/user/<artist_id>/dms")
def get_dms(service: str, artist_id: str):
    # pagination might be added at some point if we need it, but considering how few dms most artists end up having, we probably won't
    # base = request.args.to_dict()
    # base.pop('o', None)
    # base["service"] = service
    # base["artist_id"] = artist_id

    # offset = int(request.args.get('o') or 0)
    # query = request.args.get('q')
    # limit = limit_int(int(request.args.get('limit') or 25), 50)

    artist = get_artist(service, artist_id)
    if artist is None:
        return redirect(url_for("artists.list"))
    elif artist["public_id"] == artist_id and artist["id"] != artist_id:
        return redirect(url_for("artists.get_dms", service=service, artist_id=artist["id"]), code=301)

    dms = get_artist_dms(service, artist_id)

    props = ArtistDMsProps(
        id=artist_id,
        service=service,
        session=session,
        artist=artist,
        display_data=make_artist_display_data(artist),
        share_count=get_artist_share_count(service, artist_id),
        dm_count=len(dms),
        dms=dms,
        has_links="✔️" if artist["relation_id"] else "0",
    )

    response = make_response(
        render_template(
            "artist/dms.html",
            props=props,
        ),
        200,
    )
    response.headers["Cache-Control"] = "s-maxage=60"
    return response


@artists_bp.route("/<service>/user/<artist_id>/announcements")
def get_announcements(service: str, artist_id: str) -> Response:
    # offset = int(request.args.get("o") or 0)
    query = request.args.get("q", "")

    artist = get_artist(service, artist_id)
    if artist is None:
        return redirect(url_for("artists.list"))
    elif artist["public_id"] == artist_id and artist["id"] != artist_id:
        return redirect(url_for("artists.get_announcements", service=service, artist_id=artist["id"]), code=301)

    announcements = get_artist_announcements(service, artist_id, query=query, reload=True)
    # total_announcement_count = get_announcement_count(service=service, artist_id=artist_id, query=query, reload=True)

    props = ArtistAnnouncementsProps(
        id=artist_id,
        service=service,
        artist=artist,
        announcements=announcements,
        # count=total_announcement_count,
        share_count=get_artist_share_count(service, artist_id),
        dm_count=count_user_dms(service, artist_id),
        has_links="✔️" if artist["relation_id"] else "0",
        session=session,
        display_data=make_artist_display_data(artist),
    )
    response: Response = make_response(
        render_template(
            "artist/announcements.html",
            props=props,
            base={"service": service, "artist_id": artist_id},
        ),
        200,
    )
    response.headers["Cache-Control"] = "s-maxage=60"
    return response


@artists_bp.get("/<service>/user/<artist_id>/links")
def get_linked_accounts(service: str, artist_id: str):
    artist = get_artist(service, artist_id)
    if not artist:
        return redirect(url_for("artists.list"))
    elif artist["public_id"] == artist_id and artist["id"] != artist_id:
        return redirect(url_for("artists.get_linked_accounts", service=service, artist_id=artist["id"]), code=301)
    links = get_linked_creators(service, artist_id)

    props = LinkedAccountsProps(
        id=artist_id,
        service=service,
        artist=artist,
        share_count=get_artist_share_count(service, artist_id),
        dm_count=count_user_dms(service, artist_id),
        has_links="✔️" if artist["relation_id"] else "0",
        display_data=make_artist_display_data(artist),
    )

    response = make_response(
        render_template(
            "artist/linked_accounts.html",
            props=props,
            links=links,
            base={"service": service, "artist_id": artist_id},
        ),
        200
    )
    response.headers["Cache-Control"] = "s-maxage=60"
    return response


@artists_bp.delete("/<service>/user/<creator_id>/links")
@require_login
def delete_linked_account(service: str, creator_id: str, user: Account):
    if user.role != "administrator":
        abort(404)
    else:
        delete_creator_link(service, creator_id)
        return "", 204


@artists_bp.get("/<service>/user/<artist_id>/links/new")
@require_login
def get_new_link_page(service: str, artist_id: str, user: Account):
    artist = get_artist(service, artist_id)
    if not artist:
        return redirect(url_for("artists.list"))
    elif artist["public_id"] == artist_id and artist["id"] != artist_id:
        return redirect(url_for("artists.get_new_link_page", service=service, artist_id=artist["id"]), code=301)

    props = LinkedAccountsProps(
        id=artist_id,
        service=service,
        artist=artist,
        share_count=get_artist_share_count(service, artist_id),
        dm_count=count_user_dms(service, artist_id),
        has_links="✔️" if artist["relation_id"] else "0",
        display_data=make_artist_display_data(artist),
    )

    response = make_response(
        render_template(
            "artist/new_linked_account.html",
            props=props,
            base={"service": service, "artist_id": artist_id},
        ),
        200
    )
    response.headers["Cache-Control"] = "s-maxage=600"
    return response


@artists_bp.post("/<service>/user/<artist_id>/links/new")
@require_login
def post_new_link_page(service: str, artist_id: str, user: Account):
    dest_service, dest_artist_id = request.form.get("creator", "/").split("/")
    reason = request.form.get("reason", "")

    from_artist = get_artist(service, artist_id)
    to_artist = get_artist(dest_service, dest_artist_id)

    if not from_artist:
        return redirect(url_for("artists.list"))
    elif from_artist["public_id"] == artist_id and from_artist["id"] != artist_id:
        return redirect(url_for("artists.post_new_link_page", service=service, artist_id=from_artist["id"]), code=301)

    props = LinkedAccountsProps(
        id=artist_id,
        service=service,
        artist=from_artist,
        share_count=get_artist_share_count(service, artist_id),
        dm_count=count_user_dms(service, artist_id),
        has_links="✔️" if from_artist["relation_id"] else "0",
        display_data=make_artist_display_data(from_artist),
    )

    tmpl = render_template(
        "artist/new_linked_account.html",
        props=props,
        base={"service": service, "artist_id": artist_id},
    )
    if not to_artist:
        flash(f"Invalid creator (svc: {dest_service}, id: {dest_artist_id})")
        response = make_response(tmpl, 404)
        return response

    if len(reason) > 140:
        flash("Reason is too long")
        return tmpl, 422

    if dest_service == service and dest_artist_id == artist_id:
        flash("Can't link an artist to themself")
        response = make_response(tmpl, 422)
        return response

    if from_artist["relation_id"] == to_artist["relation_id"] and from_artist["relation_id"] is not None:
        flash("Already linked")
        response = make_response(tmpl, 422)
        return response

    create_unapproved_link_request(from_artist, to_artist, user.id, reason)
    flash("Request created. It will be shown here when approved.")
    return redirect(url_for("artists.get_linked_accounts", service=service, artist_id=artist_id))


def do_artist_post_search(artist_id, service, search, o, limit):
    posts = get_all_posts_by_artist(artist_id, service)
    search = search.lower()

    matches = []
    for post in posts:
        if (
            search in post["content"].lower()
            or search in post["title"].lower()
            or search in " ".join(post["tags"] or []).lower()
        ):
            matches.append(post)

    matches = sort_dict_list_by(matches, "published", True)

    return take(limit, offset_list(o, matches)), len(matches)


def make_artist_display_data(artist: dict) -> ArtistDisplayData:
    service_name: str = artist["service"]
    pay_site: Paysite | None = getattr(Paysites, service_name, None)

    if pay_site:
        return ArtistDisplayData(service=pay_site.title, href=pay_site.user.profile(artist))
    raise Exception("Service not found in Paysites")
