import re
from http import HTTPStatus
from unittest.mock import patch

import pytest
from fastapi.testclient import TestClient

from aurweb import db, time
from aurweb.asgi import app
from aurweb.aur_redis import redis_connection
from aurweb.models.account_type import USER_ID
from aurweb.models.package import Package
from aurweb.models.package_base import PackageBase
from aurweb.models.package_comaintainer import PackageComaintainer
from aurweb.models.package_request import PackageRequest
from aurweb.models.request_type import DELETION_ID, RequestType
from aurweb.models.user import User
from aurweb.testing.html import parse_root
from aurweb.testing.requests import Request

client = TestClient(app)


@pytest.fixture(autouse=True)
def setup(db_test):
    return


@pytest.fixture
def user():
    with db.begin():
        user = db.create(
            User,
            Username="test",
            Email="test@example.org",
            Passwd="testPassword",
            AccountTypeID=USER_ID,
        )
    yield user


@pytest.fixture
def user2():
    with db.begin():
        user = db.create(
            User,
            Username="test2",
            Email="test2@example.org",
            Passwd="testPassword",
            AccountTypeID=USER_ID,
        )
    yield user


@pytest.fixture
def redis():
    redis = redis_connection()

    def delete_keys():
        # Cleanup keys if they exist.
        for key in (
            "package_count",
            "orphan_count",
            "user_count",
            "trusted_user_count",
            "seven_days_old_added",
            "seven_days_old_updated",
            "year_old_updated",
            "never_updated",
            "package_updates",
        ):
            if redis.get(key) is not None:
                redis.delete(key)

    delete_keys()
    yield redis
    delete_keys()


@pytest.fixture
def package(user: User) -> Package:
    now = time.utcnow()
    with db.begin():
        pkgbase = db.create(
            PackageBase,
            Name="test-pkg",
            Maintainer=user,
            Packager=user,
            SubmittedTS=now,
            ModifiedTS=now,
        )
        pkg = db.create(Package, PackageBase=pkgbase, Name=pkgbase.Name)
    yield pkg


@pytest.fixture
def packages(user):
    """Yield a list of num_packages Package objects maintained by user."""
    num_packages = 50  # Tunable

    # For i..num_packages, create a package named pkg_{i}.
    pkgs = []
    now = time.utcnow()
    with db.begin():
        for i in range(num_packages):
            pkgbase = db.create(
                PackageBase,
                Name=f"pkg_{i}",
                Maintainer=user,
                Packager=user,
                SubmittedTS=now,
                ModifiedTS=now,
            )
            pkg = db.create(Package, PackageBase=pkgbase, Name=pkgbase.Name)
            pkgs.append(pkg)
            now += 1

    yield pkgs


def test_homepage():
    with client as request:
        response = request.get("/")
    assert response.status_code == int(HTTPStatus.OK)


@patch("aurweb.util.get_ssh_fingerprints")
def test_homepage_ssh_fingerprints(get_ssh_fingerprints_mock, user):
    fingerprints = {"Ed25519": "SHA256:RFzBCUItH9LZS0cKB5UE6ceAYhBD5C8GeOBip8Z11+4"}
    get_ssh_fingerprints_mock.return_value = fingerprints

    # without authentication (Home)
    with client as request:
        response = request.get("/")

    # with authentication (Dashboard)
    with client as auth_request:
        auth_request.cookies = {"AURSID": user.login(Request(), "testPassword")}
        auth_response = auth_request.get("/")

    for resp in [response, auth_response]:
        for key, value in fingerprints.items():
            assert key in resp.content.decode()
            assert value in resp.content.decode()
        assert (
            "The following SSH fingerprints are used for the AUR"
            in resp.content.decode()
        )


@patch("aurweb.util.get_ssh_fingerprints")
def test_homepage_no_ssh_fingerprints(get_ssh_fingerprints_mock, user):
    get_ssh_fingerprints_mock.return_value = {}

    # without authentication (Home)
    with client as request:
        response = request.get("/")

    # with authentication (Dashboard)
    with client as auth_request:
        auth_request.cookies = {"AURSID": user.login(Request(), "testPassword")}
        auth_response = auth_request.get("/")

    for resp in [response, auth_response]:
        assert (
            "The following SSH fingerprints are used for the AUR"
            not in resp.content.decode()
        )


def test_homepage_stats(redis, packages):
    with client as request:
        response = request.get("/")
    assert response.status_code == int(HTTPStatus.OK)

    root = parse_root(response.text)

    expectations = [
        ("Packages", r"\d+"),
        ("Orphan Packages", r"\d+"),
        ("Packages added in the past 7 days", r"\d+"),
        ("Packages updated in the past 7 days", r"\d+"),
        ("Packages updated in the past year", r"\d+"),
        ("Packages never updated", r"\d+"),
        ("Registered Users", r"\d+"),
        ("Trusted Users", r"\d+"),
    ]

    stats = root.xpath('//div[@id="pkg-stats"]//tr')
    for i, expected in enumerate(expectations):
        expected_key, expected_regex = expected
        key, value = stats[i].xpath("./td")
        assert key.text.strip() == expected_key
        assert re.match(expected_regex, value.text.strip())


def test_homepage_updates(redis, packages):
    with client as request:
        response = request.get("/")
        assert response.status_code == int(HTTPStatus.OK)
        # Run the request a second time to exercise the Redis path.
        response = request.get("/")
    assert response.status_code == int(HTTPStatus.OK)

    root = parse_root(response.text)

    # We expect to see the latest 15 packages, which happens to be
    # pkg_49 .. pkg_34. So, create a list of expectations using a range
    # starting at 49, stepping down to 49 - 15, -1 step at a time.
    expectations = [f"pkg_{i}" for i in range(50 - 1, 50 - 1 - 15, -1)]
    updates = root.xpath('//div[@id="pkg-updates"]/table/tbody/tr')
    for i, expected in enumerate(expectations):
        pkgname = updates[i].xpath("./td/a").pop(0)
        assert pkgname.text.strip() == expected


def test_homepage_dashboard(redis, packages, user):
    # Create Comaintainer records for all of the packages.
    with db.begin():
        for pkg in packages:
            db.create(
                PackageComaintainer, PackageBase=pkg.PackageBase, User=user, Priority=1
            )

    cookies = {"AURSID": user.login(Request(), "testPassword")}
    with client as request:
        request.cookies = cookies
        response = request.get("/")
    assert response.status_code == int(HTTPStatus.OK)

    root = parse_root(response.text)

    # Assert some expectations that we end up getting all fifty
    # packages in the "My Packages" table.
    expectations = [f"pkg_{i}" for i in range(50 - 1, 0, -1)]
    my_packages = root.xpath('//table[@id="my-packages"]/tbody/tr')
    for i, expected in enumerate(expectations):
        name, version, votes, pop, voted, notify, desc, maint = my_packages[i].xpath(
            "./td"
        )
        assert name.xpath("./a").pop(0).text.strip() == expected

    # Do the same for the Comaintained Packages table.
    my_packages = root.xpath('//table[@id="comaintained-packages"]/tbody/tr')
    for i, expected in enumerate(expectations):
        name, version, votes, pop, voted, notify, desc, maint = my_packages[i].xpath(
            "./td"
        )
        assert name.xpath("./a").pop(0).text.strip() == expected


def test_homepage_dashboard_requests(redis, packages, user):
    now = time.utcnow()

    pkg = packages[0]
    reqtype = db.query(RequestType, RequestType.ID == DELETION_ID).first()
    with db.begin():
        pkgreq = db.create(
            PackageRequest,
            PackageBase=pkg.PackageBase,
            PackageBaseName=pkg.PackageBase.Name,
            User=user,
            Comments=str(),
            ClosureComment=str(),
            RequestTS=now,
            RequestType=reqtype,
        )

    cookies = {"AURSID": user.login(Request(), "testPassword")}
    with client as request:
        request.cookies = cookies
        response = request.get("/")
    assert response.status_code == int(HTTPStatus.OK)

    root = parse_root(response.text)
    request = root.xpath('//table[@id="pkgreq-results"]/tbody/tr').pop(0)
    pkgname = request.xpath("./td/a").pop(0)
    assert pkgname.text.strip() == pkgreq.PackageBaseName


def test_homepage_dashboard_flagged_packages(redis, packages, user):
    # Set the first Package flagged by setting its OutOfDateTS column.
    pkg = packages[0]
    with db.begin():
        pkg.PackageBase.OutOfDateTS = time.utcnow()

    cookies = {"AURSID": user.login(Request(), "testPassword")}
    with client as request:
        request.cookies = cookies
        response = request.get("/")
    assert response.status_code == int(HTTPStatus.OK)

    # Check to see that the package showed up in the Flagged Packages table.
    root = parse_root(response.text)
    flagged_pkg = root.xpath('//table[@id="flagged-packages"]/tbody/tr').pop(0)
    flagged_name = flagged_pkg.xpath("./td/a").pop(0)
    assert flagged_name.text.strip() == pkg.Name


def test_homepage_dashboard_flagged(user: User, user2: User, package: Package):
    pkgbase = package.PackageBase

    now = time.utcnow()
    with db.begin():
        db.create(PackageComaintainer, User=user2, PackageBase=pkgbase, Priority=1)
        pkgbase.OutOfDateTS = now - 5
        pkgbase.Flagger = user

    # Test that a comaintainer viewing the dashboard shows them their
    # flagged co-maintained packages.
    comaint_cookies = {"AURSID": user2.login(Request(), "testPassword")}
    with client as request:
        request.cookies = comaint_cookies
        resp = request.get("/")
    assert resp.status_code == int(HTTPStatus.OK)

    root = parse_root(resp.text)
    flagged = root.xpath('//table[@id="flagged-packages"]//tr/td/a')[0]
    assert flagged.text.strip() == package.Name

    # Test that a maintainer viewing the dashboard shows them their
    # flagged maintained packages.
    cookies = {"AURSID": user.login(Request(), "testPassword")}
    with client as request:
        request.cookies = cookies
        resp = request.get("/")
    assert resp.status_code == int(HTTPStatus.OK)

    root = parse_root(resp.text)
    flagged = root.xpath('//table[@id="flagged-packages"]//tr/td/a')[0]
    assert flagged.text.strip() == package.Name
