from datetime import datetime, timedelta, timezone
from typing import Optional

from flask import Blueprint, render_template, request
from flask.typing import ResponseReturnValue
from jinja2.markupsafe import Markup
from sqlalchemy import desc, or_

from app.alarms import alarms_for
from app.models.activity import Activity
from app.models.alarms import Alarm, AlarmState
from app.models.bridges import Bridge
from app.models.mirrors import Origin, Proxy
from app.models.base import Group
from app.models.onions import Eotk
from app.portal.automation import bp as automation
from app.portal.bridgeconf import bp as bridgeconf
from app.portal.bridge import bp as bridge
from app.portal.eotk import bp as eotk
from app.portal.group import bp as group
from app.portal.list import bp as list_
from app.portal.origin import bp as origin
from app.portal.onion import bp as onion
from app.portal.proxy import bp as proxy
from app.portal.smart_proxy import bp as smart_proxy
from app.portal.webhook import bp as webhook

portal = Blueprint("portal", __name__, template_folder="templates", static_folder="static")
portal.register_blueprint(automation, url_prefix="/automation")
portal.register_blueprint(bridgeconf, url_prefix="/bridgeconf")
portal.register_blueprint(bridge, url_prefix="/bridge")
portal.register_blueprint(eotk, url_prefix="/eotk")
portal.register_blueprint(group, url_prefix="/group")
portal.register_blueprint(list_, url_prefix="/list")
portal.register_blueprint(origin, url_prefix="/origin")
portal.register_blueprint(onion, url_prefix="/onion")
portal.register_blueprint(proxy, url_prefix="/proxy")
portal.register_blueprint(smart_proxy, url_prefix="/smart")
portal.register_blueprint(webhook, url_prefix="/webhook")


@portal.app_template_filter("mirror_expiry")
def calculate_mirror_expiry(s: datetime) -> str:
    expiry = s + timedelta(days=3)
    countdown = expiry - datetime.utcnow()
    if countdown.days == 0:
        return f"{countdown.seconds // 3600} hours"
    return f"{countdown.days} days"


@portal.app_template_filter("format_datetime")
def format_datetime(s: Optional[datetime]) -> str:
    if s is None:
        return "Unknown"
    return s.strftime("%a, %d %b %Y %H:%M:%S")


@portal.app_template_filter("describe_brn")
def describe_brn(s: str) -> ResponseReturnValue:
    parts = s.split(":")
    if parts[3] == "mirror":
        if parts[5].startswith("origin/"):
            origin = Origin.query.filter(
                Origin.domain_name == parts[5][len("origin/"):]
            ).first()
            if not origin:
                return s
            return f"Origin: {origin.domain_name} ({origin.group.group_name})"
        if parts[5].startswith("proxy/"):
            proxy = Proxy.query.filter(
                Proxy.id == int(parts[5][len("proxy/"):])
            ).first()
            if not proxy:
                return s
            return Markup(  # type: ignore[no-untyped-call]
                f"Proxy: {proxy.url}<br>({proxy.origin.group.group_name}: {proxy.origin.domain_name})")
        if parts[5].startswith("quota/"):
            if parts[4] == "cloudfront":
                return f"Quota: CloudFront {parts[5][len('quota/'):]}"
    if parts[3] == "eotk":
        if parts[5].startswith("instance/"):
            eotk = Eotk.query.filter(
                Eotk.group_id == parts[2],
                Eotk.region == parts[5][len("instance/"):]
            ).first()
            if not eotk:
                return s
            return f"EOTK Instance: {eotk.group.group_name} in {eotk.provider} {eotk.region}"
    return s


def total_origins_blocked() -> int:
    count = 0
    for o in Origin.query.filter(Origin.destroyed.is_(None)).all():
        for a in alarms_for(o.brn):
            if a.aspect.startswith("origin-block-ooni-"):
                if a.alarm_state == AlarmState.WARNING:
                    count += 1
                    break
    return count


@portal.route("/")
def portal_home() -> ResponseReturnValue:
    groups = Group.query.order_by(Group.group_name).all()
    now = datetime.now(timezone.utc)
    proxies = Proxy.query.filter(Proxy.destroyed.is_(None)).all()
    last24 = len(Proxy.query.filter(Proxy.deprecated > (now - timedelta(days=1))).all())
    last72 = len(Proxy.query.filter(Proxy.deprecated > (now - timedelta(days=3))).all())
    lastweek = len(Proxy.query.filter(Proxy.deprecated > (now - timedelta(days=7))).all())
    alarms = {
        s: len(Alarm.query.filter(Alarm.alarm_state == s.upper(), Alarm.last_updated > (now - timedelta(days=1))).all())
        for s in ["critical", "warning", "ok", "unknown"]
    }
    bridges = Bridge.query.filter(Bridge.destroyed.is_(None)).all()
    br_last = {
        d: len(Bridge.query.filter(Bridge.deprecated > (now - timedelta(days=d))).all())
        for d in [1, 3, 7]
    }
    activity = Activity.query.filter(Activity.added > (now - timedelta(days=2))).order_by(desc(Activity.added)).all()
    onionified = len([o for o in Origin.query.filter(Origin.destroyed.is_(None)).all() if o.onion() is not None])
    ooni_blocked = total_origins_blocked()
    total_origins = len(Origin.query.filter(Origin.destroyed.is_(None)).all())
    return render_template("home.html.j2", section="home", groups=groups, last24=last24, last72=last72,
                           lastweek=lastweek, proxies=proxies, **alarms, activity=activity, total_origins=total_origins,
                           onionified=onionified, br_last=br_last, ooni_blocked=ooni_blocked, bridges=bridges)


@portal.route("/search")
def search() -> ResponseReturnValue:
    query = request.args.get("query")
    proxies = Proxy.query.filter(or_(Proxy.url.contains(query)), Proxy.destroyed.is_(None)).all()
    origins = Origin.query.filter(or_(Origin.description.contains(query), Origin.domain_name.contains(query))).all()
    return render_template("search.html.j2", section="home", proxies=proxies, origins=origins)


@portal.route('/alarms')
def view_alarms() -> ResponseReturnValue:
    one_day_ago = datetime.now(timezone.utc) - timedelta(days=1)
    alarms = Alarm.query.filter(Alarm.last_updated >= one_day_ago).order_by(
        desc(Alarm.alarm_state), desc(Alarm.state_changed)).all()
    return render_template("list.html.j2",
                           section="alarm",
                           title="Alarms",
                           items=alarms)
