import datetime
import logging
from typing import cast, get_args

from flask import Blueprint, Response, make_response, redirect, render_template, request, url_for
from psycopg.errors import QueryCanceled
from src.lib.files import get_archive_files

from src.config import Configuration
from src.lib.post import get_render_data_for_posts
from src.lib.posts import (
    count_all_posts,
    count_all_posts_for_query,
    count_all_posts_for_tag,
    get_all_posts_for_query,
    get_all_posts_summary,
    get_all_tags,
    get_popular_posts_for_date_range,
    get_tagged_posts,
)
from src.utils.datetime_ import PeriodScale, parse_scale_string
from src.utils.utils import (
    get_query_parameters_dict,
    limit_int,
    parse_int,
    parse_offset,
    positive_or_none,
    set_query_parameter,
    step_int,
)

posts_bp = Blueprint("posts", __name__)


@posts_bp.route("/posts")
def get_posts():
    props = {
        "currentPage": "posts",
        "limit": 50,
    }

    query_params = get_query_parameters_dict(request, on_errors="ignore", clean_query_string=True)

    extra_pages = Configuration().webserver["extra_pages_to_load_on_posts"]
    max_offset = props["limit"] * 1000  # only load 1000 pages of any result
    query = query_params.get("q", "").strip()[: Configuration().webserver["max_full_text_search_input_len"]]
    tags = request.args.getlist("tag")
    o = query_params.pop("o", 0)
    offset = positive_or_none(step_int(parse_int(o, 0), props["limit"]))
    if offset is None or offset > max_offset:
        return redirect(url_for("posts.get_posts"))
    extra_offset = positive_or_none(step_int(parse_int(o, 0), props["limit"] * extra_pages))
    slice_offset = offset - extra_offset
    # todo this true_count and count have no real meaning seeing it is displayed as {{props.true_count or props.count }}
    if tags:
        extra_results = get_tagged_posts(tags, extra_offset, props["limit"] * extra_pages)
        total_count = count_all_posts_for_tag(tags)
        props["true_count"] = total_count
        props["count"] = limit_int(total_count, max_offset)
    elif not query or len(query) < 2:
        extra_results = get_all_posts_summary(extra_offset, props["limit"] * extra_pages, cache_ttl=Configuration().cache_ttl_for_recent_posts)[
            slice_offset : props["limit"] + slice_offset
        ]
        props["true_count"] = count_all_posts()
        props["count"] = limit_int(count_all_posts(), max_offset)
    else:
        try:
            extra_results = get_all_posts_for_query(query, extra_offset, props["limit"] * extra_pages)
        except QueryCanceled:
            return make_response("Query Timeout. Please fix your query text or try again later.", 408)
        except Exception as error:
            if "failed to parse expression" not in str(error):
                raise
            query = "Failed to parse query."
            query_params["q"] = query
            extra_results = get_all_posts_for_query(query, extra_offset, props["limit"] * extra_pages)

        if not offset and len(extra_results) < props["limit"]:
            props["true_count"] = 0
            props["count"] = len(extra_results)
        else:
            try:
                props["true_count"] = count_all_posts_for_query(query)
                props["count"] = limit_int(props["true_count"], max_offset)
            except Exception as count_error:  # catch timeouts, set count as max offset
                logging.exception(
                    "Caught error in count_all_posts_for_query",
                    extra={"e": count_error},
                )
                props["true_count"] = 0
                props["count"] = max_offset

    results = extra_results[slice_offset : props["limit"] + slice_offset]

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

    response = make_response(
        render_template(
            "posts.html",
            props=props,
            results=results,
            base=query_params,
            result_previews=result_previews,
            result_attachments=result_attachments,
            result_is_image=result_is_image,
        ),
        200,
    )
    # response.headers["Cache-Control"] = "no-store, max-age=0"
    return response


@posts_bp.route("/posts/popular")
def popular_posts() -> Response:
    query_params = get_query_parameters_dict(request)
    earliest_date_for_popular = Configuration().webserver.get("earliest_date_for_popular")
    # checked below but doesn't typecheck without a cast
    scale: PeriodScale = cast(PeriodScale, query_params.get("period", "recent"))

    if scale not in get_args(PeriodScale):
        scale = "recent"

    info, valid_date = parse_scale_string(query_params.get("date"), scale)
    does_not_match_step_date = scale != "recent" and info.date.date() != info.navigation_dates[scale][2]
    if (
        not valid_date
        or does_not_match_step_date
        or info.date.date() > datetime.date.today()
        or info.date.date() < earliest_date_for_popular
    ):
        correct_date = info.navigation_dates[scale][2].isoformat()
        if info.date.date() > datetime.date.today():
            correct_date = datetime.date.today()
            scale = "day"
        elif info.date.date() < earliest_date_for_popular:
            correct_date = earliest_date_for_popular
            scale = "day"
        new_url = set_query_parameter(url_for("posts.popular_posts"), {"date": correct_date, "period": scale})
        response = redirect(new_url)
        cache_seconds = int(datetime.timedelta(days=7).total_seconds())
        if info.date.date() > datetime.date.today():
            cache_seconds = int(datetime.timedelta(hours=3).total_seconds())
        response.headers["Cache-Control"] = f"max-age={cache_seconds}"
        return response
    expiry = int(datetime.timedelta(days=30).total_seconds())
    if scale == "recent":
        expiry = int(datetime.timedelta(minutes=30 + 1).total_seconds())
    elif info.max_date > datetime.datetime.utcnow():
        if scale == "day":
            expiry = int(datetime.timedelta(hours=3).total_seconds())
        elif scale == "week":
            expiry = int(datetime.timedelta(days=1).total_seconds())
        elif scale == "month":
            if datetime.date.today().day < 7:
                expiry = int(datetime.timedelta(days=1).total_seconds())
            else:
                expiry = int(datetime.timedelta(days=5).total_seconds())

    pages = Configuration().webserver.get("pages_in_popular")
    per_page = 50
    offset = positive_or_none(step_int(parse_int(query_params.pop("o", 0), 0), per_page))
    if offset is None:
        response = redirect(url_for("posts.popular_posts"))
        response.headers["Cache-Control"] = f"max-age={int(datetime.timedelta(days=7).total_seconds())}"
        return response
    posts = get_popular_posts_for_date_range(
        info.min_date, info.max_date, scale, offset // per_page, per_page, pages, expiry
    )
    (previews, attachments, is_image) = get_render_data_for_posts(posts)

    response = make_response(
        render_template(
            "posts/popular.html",
            info=info,
            props={
                "currentPage": "popular_posts",
                "today": datetime.date.today(),
                "earliest_date_for_popular": Configuration().webserver.get("earliest_date_for_popular"),
                "limit": per_page,
                "count": pages * per_page,
            },
            results=posts,
            base=query_params,
            result_previews=previews,
            result_attachments=attachments,
            result_is_image=is_image,
        ),
        200,
    )
    response.headers["Cache-Control"] = f"max-age={int(expiry)}"
    return response


@posts_bp.get("/posts/tags")
def list_tags():
    response = make_response(
        render_template(
            "tags.html",
            props={"currentPage": "tags"},
            tags=get_all_tags(),
        ),
    )
    response.headers["Cache-Control"] = "s-maxage=3600"
    return response


@posts_bp.route("/discord/server/<server_id>")
def discord_server(server_id):
    response = make_response(render_template("discord.html"), 200)
    response.headers["Cache-Control"] = "s-maxage=600"
    return response


@posts_bp.route("/posts/archives/<file_hash>")
def list_archive(file_hash: str):
    archive = get_archive_files(file_hash)
    response = make_response(render_template(
        "posts/archive.html",
        props={},
        archive=archive,
        file_serving_enabled=Configuration().archive_server["file_serving_enabled"],
    ), 200)
    response.headers["Cache-Control"] = "s-maxage=600"
    return response
