import datetime
import os
from unittest.mock import patch

import pytest
import smpplib
from babel.messages.frontend import compile_catalog
from flask_webtest import TestApp
from jinja2 import FileSystemBytecodeCache
from jinja2 import StrictUndefined
from pytest_lazy_fixtures import lf
from werkzeug.security import gen_salt

from canaille import create_app
from canaille.app import models
from canaille.backends import available_backends


@pytest.fixture(autouse=True, scope="session")
def babel_catalogs():
    cmd = compile_catalog()
    cmd.directory = os.path.dirname(__file__) + "/../canaille/translations"
    cmd.quiet = True
    cmd.statistics = True
    cmd.finalize_options()
    cmd.run()


pytest_plugins = [
    f"tests.backends.{backend}.fixtures"
    for backend in available_backends()
    if os.path.exists(os.path.join("tests", "backends", backend, "fixtures.py"))
]


def pytest_addoption(parser):
    parser.addoption(
        "--backend", action="append", default=[], help="the backends to test"
    )


def pytest_generate_tests(metafunc):
    backends = available_backends()
    if metafunc.config.getoption("backend"):  # pragma: no cover
        backends &= set(metafunc.config.getoption("backend"))

    # sort so the backend list is deterministic. This makes pytest-xdist discovery possible
    backends = sorted(backends)

    # tests in tests.backends.BACKENDNAME should only run one backend
    module_name_parts = metafunc.module.__name__.split(".")
    if module_name_parts[0:2] == ["tests", "backends"] and len(module_name_parts) > 3:
        backend = module_name_parts[2]
        if backend not in backends:  # pragma: no cover
            pytest.skip()
        elif "backend" in metafunc.fixturenames:
            metafunc.parametrize("backend", [lf(f"{backend}_backend")])
            return

    elif "backend" in metafunc.fixturenames:
        metafunc.parametrize(
            "backend",
            [lf(f"{backend}_backend") for backend in backends],
        )


@pytest.fixture
def configuration(smtpd):
    smtpd.config.use_starttls = True
    conf = {
        "SECRET_KEY": gen_salt(24),
        "SERVER_NAME": "canaille.test",
        "PREFERRED_URL_SCHEME": "http",
        "TESTING": True,
        "CANAILLE": {
            "JAVASCRIPT": False,
            "LOGO": "/static/img/canaille-head.webp",
            "TIMEZONE": "UTC",
            "ACL": {
                "DEFAULT": {
                    "READ": ["user_name", "groups"],
                    "PERMISSIONS": ["edit_self", "use_oidc"],
                    "WRITE": [
                        "emails",
                        "given_name",
                        "photo",
                        "family_name",
                        "display_name",
                        "password",
                        "phone_numbers",
                        "formatted_address",
                        "street",
                        "postal_code",
                        "locality",
                        "region",
                        "employee_number",
                        "department",
                        "preferred_language",
                        "title",
                        "organization",
                        "lock_date",
                    ],
                },
                "ADMIN": {
                    "FILTER": [{"user_name": "admin"}, {"family_name": "admin"}],
                    "PERMISSIONS": [
                        "manage_users",
                        "manage_groups",
                        "manage_oidc",
                        "delete_account",
                        "impersonate_users",
                    ],
                    "WRITE": [
                        "groups",
                        "lock_date",
                    ],
                },
                "MODERATOR": {
                    "FILTER": [
                        {"user_name": "moderator"},
                        {"family_name": "moderator"},
                    ],
                    "PERMISSIONS": ["manage_users", "manage_groups", "delete_account"],
                    "WRITE": [
                        "groups",
                    ],
                },
            },
            "SMTP": {
                "HOST": smtpd.hostname,
                "PORT": smtpd.port,
                "TLS": smtpd.config.use_starttls,
                "SSL": smtpd.config.use_ssl,
                "LOGIN": smtpd.config.login_username,
                "PASSWORD": smtpd.config.login_password,
                "FROM_ADDR": "admin@mydomain.test",
            },
            "SMPP": {
                "HOST": "localhost",
                "PORT": 2775,
                "LOGIN": "user",
                "PASSWORD": "user",
            },
            "LOGGING": {
                "version": 1,
                "formatters": {
                    "default": {
                        "format": "[%(asctime)s] - %(ip)s - %(levelname)s in %(module)s: %(message)s",
                    }
                },
                "handlers": {
                    "wsgi": {
                        "class": "logging.StreamHandler",
                        "stream": "ext://sys.stdout",
                        "formatter": "default",
                    }
                },
                "loggers": {
                    "faker": {"level": "WARNING"},
                    "canaille": {"level": "DEBUG", "handlers": ["wsgi"]},
                },
                "disable_existing_loggers": False,
            },
            "ADMIN_EMAIL": "admin_default_mail@mydomain.test",
            "PASSWORD_COMPROMISSION_CHECK_API_URL": "https://api.pwnedpasswords.test/range/",
        },
    }
    return conf


@pytest.fixture(scope="session")
def jinja_cache_directory(tmp_path_factory):
    return tmp_path_factory.mktemp("cache")


@pytest.fixture
def app(configuration, backend, jinja_cache_directory):
    app = create_app(configuration, backend=backend)
    # caches the Jinja compiled files for faster unit test execution
    app.jinja_env.bytecode_cache = FileSystemBytecodeCache(jinja_cache_directory)
    with app.app_context():
        backend.install(app)
    with app.test_request_context():
        yield app


@pytest.fixture
def testclient(app):
    app.jinja_env.undefined = StrictUndefined
    yield TestApp(app)


@pytest.fixture
def user(app, backend):
    u = models.User(
        formatted_name="John (johnny) Doe",
        given_name="John",
        family_name="Doe",
        user_name="user",
        emails=["john@doe.test"],
        password="correct horse battery staple",
        display_name="Johnny",
        preferred_language="en",
        phone_numbers=["555-000-000"],
        profile_url="https://john.test",
        formatted_address="1235, somewhere",
    )
    backend.save(u)
    yield u
    backend.delete(u)


@pytest.fixture
def user_otp(app, user, backend):
    user.secret_token = (
        "fefe9b106b8a033d3fcb4de16ac06b2cae71c7d95a41b158c30380d1bc35b2ba"
    )
    user.hotp_counter = 1
    user.last_otp_login = datetime.datetime(2020, 1, 1)
    backend.save(user)
    yield user


@pytest.fixture
def admin(app, backend):
    u = models.User(
        formatted_name="Jane Doe",
        family_name="Doe",
        user_name="admin",
        emails=["jane@doe.test"],
        password="admin",
    )
    backend.save(u)
    yield u
    backend.delete(u)


@pytest.fixture
def moderator(app, backend):
    u = models.User(
        formatted_name="Jack Doe",
        family_name="Doe",
        user_name="moderator",
        emails=["jack@doe.test"],
        password="moderator",
    )
    backend.save(u)
    yield u
    backend.delete(u)


@pytest.fixture
def logged_user(user, testclient):
    with testclient.session_transaction() as sess:
        sess["user_id"] = [user.id]
    return user


@pytest.fixture
def logged_user_otp(user_otp, testclient):
    with testclient.session_transaction() as sess:
        sess["user_id"] = [user_otp.id]
    return user_otp


@pytest.fixture
def logged_admin(admin, testclient):
    with testclient.session_transaction() as sess:
        sess["user_id"] = [admin.id]
    return admin


@pytest.fixture
def logged_moderator(moderator, testclient):
    with testclient.session_transaction() as sess:
        sess["user_id"] = [moderator.id]
    return moderator


@pytest.fixture
def foo_group(app, user, backend):
    group = models.Group(
        members=[user],
        display_name="foo",
    )
    backend.save(group)
    backend.reload(user)
    yield group
    backend.delete(group)


@pytest.fixture
def bar_group(app, admin, backend):
    group = models.Group(
        members=[admin],
        display_name="bar",
    )
    backend.save(group)
    backend.reload(admin)
    yield group
    backend.delete(group)


@pytest.fixture
def jpeg_photo():
    return b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x01,\x01,\x00\x00\xff\xfe\x00\x13Created with GIMP\xff\xe2\x02\xb0ICC_PROFILE\x00\x01\x01\x00\x00\x02\xa0lcms\x040\x00\x00mntrRGB XYZ \x07\xe5\x00\x0c\x00\x08\x00\x0f\x00\x16\x00(acspAPPL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\xd6\x00\x01\x00\x00\x00\x00\xd3-lcms\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\rdesc\x00\x00\x01 \x00\x00\x00@cprt\x00\x00\x01`\x00\x00\x006wtpt\x00\x00\x01\x98\x00\x00\x00\x14chad\x00\x00\x01\xac\x00\x00\x00,rXYZ\x00\x00\x01\xd8\x00\x00\x00\x14bXYZ\x00\x00\x01\xec\x00\x00\x00\x14gXYZ\x00\x00\x02\x00\x00\x00\x00\x14rTRC\x00\x00\x02\x14\x00\x00\x00 gTRC\x00\x00\x02\x14\x00\x00\x00 bTRC\x00\x00\x02\x14\x00\x00\x00 chrm\x00\x00\x024\x00\x00\x00$dmnd\x00\x00\x02X\x00\x00\x00$dmdd\x00\x00\x02|\x00\x00\x00$mluc\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0cenUS\x00\x00\x00$\x00\x00\x00\x1c\x00G\x00I\x00M\x00P\x00 \x00b\x00u\x00i\x00l\x00t\x00-\x00i\x00n\x00 \x00s\x00R\x00G\x00Bmluc\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0cenUS\x00\x00\x00\x1a\x00\x00\x00\x1c\x00P\x00u\x00b\x00l\x00i\x00c\x00 \x00D\x00o\x00m\x00a\x00i\x00n\x00\x00XYZ \x00\x00\x00\x00\x00\x00\xf6\xd6\x00\x01\x00\x00\x00\x00\xd3-sf32\x00\x00\x00\x00\x00\x01\x0cB\x00\x00\x05\xde\xff\xff\xf3%\x00\x00\x07\x93\x00\x00\xfd\x90\xff\xff\xfb\xa1\xff\xff\xfd\xa2\x00\x00\x03\xdc\x00\x00\xc0nXYZ \x00\x00\x00\x00\x00\x00o\xa0\x00\x008\xf5\x00\x00\x03\x90XYZ \x00\x00\x00\x00\x00\x00$\x9f\x00\x00\x0f\x84\x00\x00\xb6\xc4XYZ \x00\x00\x00\x00\x00\x00b\x97\x00\x00\xb7\x87\x00\x00\x18\xd9para\x00\x00\x00\x00\x00\x03\x00\x00\x00\x02ff\x00\x00\xf2\xa7\x00\x00\rY\x00\x00\x13\xd0\x00\x00\n[chrm\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\xa3\xd7\x00\x00T|\x00\x00L\xcd\x00\x00\x99\x9a\x00\x00&g\x00\x00\x0f\\mluc\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0cenUS\x00\x00\x00\x08\x00\x00\x00\x1c\x00G\x00I\x00M\x00Pmluc\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0cenUS\x00\x00\x00\x08\x00\x00\x00\x1c\x00s\x00R\x00G\x00B\xff\xdb\x00C\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\xff\xdb\x00C\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\xff\xc2\x00\x11\x08\x00\x01\x00\x01\x03\x01\x11\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\xff\xc4\x00\x14\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x0c\x03\x01\x00\x02\x10\x03\x10\x00\x00\x01\x7f\x0f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x01\x00\x01\x05\x02\x7f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x03\x01\x01?\x01\x7f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x02\x01\x01?\x01\x7f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x01\x00\x06?\x02\x7f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x01\x00\x01?!\x7f\xff\xda\x00\x0c\x03\x01\x00\x02\x00\x03\x00\x00\x00\x10\x1f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x03\x01\x01?\x10\x7f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x02\x01\x01?\x10\x7f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x08\x01\x01\x00\x01?\x10\x7f\xff\xd9"


pdu = None


@pytest.fixture()
def mock_smpp(testclient):
    class MockSmppClient(smpplib.client.Client):
        def connect(self):
            pass

        def bind_transmitter(self, system_id, password):
            pass

        def send_pdu(self, p):
            global pdu
            pdu = p

    with patch("smpplib.client.Client", MockSmppClient):
        yield
