# Copyright 2023 Onestein (<https://www.onestein.eu>)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).

from datetime import datetime, timedelta
from unittest import mock

from odoo import http, registry
from odoo.exceptions import UserError, ValidationError
from odoo.tests.common import HOST, HttpCase, Opener, get_db_name, new_test_user, tagged


@tagged("-at_install", "post_install")
class TestPasswordSecurityLogin(HttpCase):
    def setUp(self):
        super().setUp()
        self.username = "jackoneill"
        self.passwd = "!asdQWE12345_3"

        # Create user with strong password: no error raised
        new_test_user(self.env, self.username, password=self.passwd)

    def login(self, username, password):
        """Log in with provided credentials."""
        self.session = http.root.session_store.new()
        self.opener = Opener(self.env.cr)
        self.opener.cookies.set("session_id", self.session.sid, domain=HOST, path="/")

        with mock.patch("odoo.http.db_filter") as db_filter:
            db_filter.side_effect = lambda dbs, host=None: [get_db_name()]
            res_post = self.url_open(
                "/web/login",
                data={
                    "login": username,
                    "password": password,
                    "csrf_token": http.Request.csrf_token(self),
                },
            )
        res_post.raise_for_status()

        return res_post

    def test_01_create_user_fail(self):
        """It should fail when creating user with short password"""
        # Short password: UserError is raised
        with self.assertRaises(UserError):
            new_test_user(self.env, "new_user", password="abc")

    def test_02_create_user_fail(self):
        """It should fail when creating user with weak password"""
        # Weak password: ValidationError is raised
        with self.assertRaises(ValidationError):
            new_test_user(self.env, "new_user", password="abcdefgh")

    def test_03_web_login_success(self):
        """Allow authenticating by login"""

        # Log in
        response = self.login(self.username, self.passwd)

        # Ensure we end up on the right page
        self.assertEqual(response.request.path_url, "/web")
        self.assertEqual(response.status_code, 200)

    def test_04_web_login_fail(self):
        """Fail authenticating with wrong password"""

        # Try to log in
        response = self.login(self.username, "wrong")

        # Ensure we stay on the login page
        self.assertEqual(response.request.path_url, "/web/login")
        self.assertEqual(response.status_code, 200)
        self.assertIn(
            "Wrong login/password",
            response.text,
        )

    def test_05_web_login_expire_pass(self):
        """It should expire password if necessary"""

        # Make password expired
        three_days_ago = datetime.now() - timedelta(days=3)

        with registry(get_db_name()).cursor() as cr:
            env = self.env(cr)
            user = env["res.users"].search([("login", "=", self.username)])
            user.password_write_date = three_days_ago
            user.company_id.password_expiration = 1

        # Try to log in
        response = self.login(self.username, self.passwd)

        # Ensure we end up on the password reset page
        self.assertIn("/web/reset_password", response.request.path_url)

    def test_06_web_login_log_out_if_expired(self):
        """It should log out user if password expired"""

        # Log in
        response = self.login(self.username, self.passwd)

        # Ensure we end up on the right page
        self.assertEqual(response.request.path_url, "/web")
        self.assertEqual(response.status_code, 200)

        # Make password expired while still logged in
        three_days_ago = datetime.now() - timedelta(days=3)

        with registry(get_db_name()).cursor() as cr:
            env = self.env(cr)
            user = env["res.users"].search([("login", "=", self.username)])
            user.password_write_date = three_days_ago
            user.company_id.password_expiration = 1

        # Try to access just a page
        req_page1 = self.url_open("/web")
        self.assertEqual(req_page1.request.path_url, "/web")
        self.assertEqual(req_page1.status_code, 200)

        # Try to log in again
        response = self.login(self.username, self.passwd)

        # Ensure we end up on the password reset page
        self.assertIn("/web/reset_password", response.request.path_url)

        # Try to access just a page: user kicked out
        req_page2 = self.url_open("/web")
        self.assertEqual("/web/login", req_page2.request.path_url)
        self.assertEqual(req_page2.status_code, 200)

    def test_07_web_login_redirect(self):
        """It should redirect w/ hash to reset after expiration"""

        # Emulate password expired
        with mock.patch(
            "odoo.addons.password_security.models.res_users.ResUsers._password_has_expired"
        ) as func_password_has_expired:
            func_password_has_expired.return_value = True

            # Try to log in
            response = self.login(self.username, self.passwd)

        # Ensure we end up on the password reset page
        self.assertIn("/web/reset_password", response.request.path_url)

        # Try to access just a page: user kicked out
        req_page = self.url_open("/web")
        self.assertEqual("/web/login", req_page.request.path_url)
        self.assertEqual(req_page.status_code, 200)
