# -*- coding: utf-8 -*-

import json
import re

from http import HTTPStatus
from urllib.parse import urlparse, parse_qs

from django import test
from django.contrib.auth.models import Permission
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError

from tcms.testcases.models import TestCaseStatus
from tcms.testruns.models import TestCaseRunStatus
from tests import factories as f

__all__ = (
    'user_should_have_perm',
    'remove_perm_from_user',
    'BasePlanCase',
    'BaseCaseRun',
    'encode',
    'HelperAssertions',
)


def encode(s):
    """For running tests in Python 3

    This is added for running tests successfully with Python 3 while porting to
    Python 3 compatibility. But, it is really necessary to think about the
    XMLPRC API that strings returned should be in bytestring or not.
    """
    if isinstance(s, str):
        return s.encode()
    else:
        return s


def user_should_have_perm(user, perm):
    if isinstance(perm, str):
        try:
            app_label, codename = perm.split('.')
        except ValueError:
            raise ValueError('%s is not valid. Should be in format app_label.perm_codename')
        else:
            if not app_label or not codename:
                raise ValueError('Invalid app_label or codename')
            get_permission = Permission.objects.get
            user.user_permissions.add(
                get_permission(content_type__app_label=app_label, codename=codename))
    elif isinstance(perm, Permission):
        user.user_permissions.add(perm)
    else:
        raise TypeError('perm should be an instance of either basestring or Permission')


def remove_perm_from_user(user, perm):
    """Remove a permission from an user"""

    if isinstance(perm, str):
        try:
            app_label, codename = perm.split('.')
        except ValueError:
            raise ValueError('%s is not valid. Should be in format app_label.perm_codename')
        else:
            if not app_label or not codename:
                raise ValueError('Invalid app_label or codename')
            get_permission = Permission.objects.get
            user.user_permissions.remove(
                get_permission(content_type__app_label=app_label, codename=codename))
    elif isinstance(perm, Permission):
        user.user_permissions.remove(perm)
    else:
        raise TypeError('perm should be an instance of either basestring or Permission')


def create_request_user(username=None, password=None):
    if username:
        user = f.UserFactory(username=username)
    else:
        user = f.UserFactory()
    if password:
        user.set_password(password)
    else:
        user.set_password('password')
    user.save()
    return user


class HelperAssertions:
    """Helper assertion methods"""

    def assert200(self, response):
        self.assertEqual(HTTPStatus.OK, response.status_code)

    def assert301(self, response):
        self.assertEqual(HTTPStatus.MOVED_PERMANENTLY, response.status_code)

    def assert302(self, response):
        self.assertEqual(HTTPStatus.FOUND, response.status_code)

    def assert400(self, response):
        self.assertEqual(HTTPStatus.BAD_REQUEST, response.status_code)

    def assert403(self, response):
        self.assertEqual(HTTPStatus.FORBIDDEN, response.status_code)

    def assert404(self, response):
        self.assertEqual(HTTPStatus.NOT_FOUND, response.status_code)

    def assert500(self, response):
        self.assertEqual(
            HTTPStatus.INTERNAL_SERVER_ERROR, response.status_code)

    def assertJsonResponse(self, response, expected, status_code=200):
        self.assertEqual(status_code, response.status_code)
        self.assertEqual(expected, json.loads(response.content))

    def assert_url(self, expected_url, url):
        """Check if two URL are same

        Assertions are called inside this this method. If anything is
        different, it will fail immediately.

        :param str expected_url: expected URL compare.
        :param str url: the URL to check if it is same as the expected URL.
        """
        url = urlparse(url)
        expected_url = urlparse(expected_url)

        self.assertEqual(expected_url.scheme, url.scheme)
        self.assertEqual(expected_url.netloc, url.netloc)
        self.assertEqual(expected_url.path, url.path)
        self.assertEqual(parse_qs(expected_url.query), parse_qs(url.query))

    def assertValidationError(self, field, message_regex, func, *args, **kwargs):
        """Assert django.core.exceptions.ValidationError is raised with expected message"""
        try:
            func(*args, **kwargs)
        except Exception as e:
            self.assertIsInstance(
                e, ValidationError, f'Exception {e} is not a ValidationError.')
            self.assertIn(field, e.message_dict,
                          f'Field {field} is not included in errors.')
            matches = [re.search(message_regex, item) is not None
                       for item in e.message_dict[field]]
            self.assertTrue(any(matches), 'Expected match message is not included.')
        else:
            self.fail('ValidationError is not raised.')


class AuthMixin(object):

    @classmethod
    def setUpTestData(cls):
        cls.tester = User.objects.create_user(
            username='nitrate-tester',
            email='nitrate-tester@example.com')
        cls.tester.set_password('password')
        cls.tester.save()

    def login_tester(self, user=None, password=None):
        """Login tester user for test

        Login pre-created tester user by default. If both user and password
        are given, login that user instead.
        """
        if user and password:
            login_user = user
            login_password = password
        else:
            login_user = self.tester
            login_password = 'password'

        self.client.login(username=login_user.username,
                          password=login_password)


class NitrateTestCase(test.TestCase):
    """Base test case for writing tests for Nitrate"""

    @classmethod
    def create_bz_tracker(cls):
        """Helper function to create a Bugzilla issue tracker"""
        return f.IssueTrackerFactory(
            name='bz',
            service_url='http://bugs.example.com/',
            issue_report_endpoint='/enter_bug.cgi',
            issue_url_fmt='http://bugs.example.com/?id={issue_key}',
            issues_display_url_fmt='http://bugs.example.com/?bug_id={issue_keys}',
            validate_regex=r'^\d+$')

    @classmethod
    def create_jira_tracker(cls):
        """Helper function to create a Bugzilla issue tracker"""
        return f.IssueTrackerFactory(
            name='jira',
            service_url='http://jira.example.com/',
            issue_report_endpoint='/enter_bug.cgi',
            issue_url_fmt='http://jira.example.com/browse/{issue_key}',
            issues_display_url_fmt='http://jira.example.com/?jql=issuekey in ({issue_keys})',
            validate_regex=r'^[A-Z]+-\d+$')


class BasePlanCase(HelperAssertions, AuthMixin, NitrateTestCase):
    """Base test case by providing essential Plan and Case objects used in tests"""

    @classmethod
    def setUpTestData(cls):
        super().setUpTestData()

        cls.case_status_confirmed = TestCaseStatus.objects.get(name='CONFIRMED')
        cls.case_status_proposed = TestCaseStatus.objects.get(name='PROPOSED')

        cls.product = f.ProductFactory(name='Nitrate')
        cls.version = f.VersionFactory(value='0.1', product=cls.product)

        cls.plan = f.TestPlanFactory(
            author=cls.tester,
            owner=cls.tester,
            product=cls.product,
            product_version=cls.version)

        cls.case = f.TestCaseFactory(
            summary='Test case 0',
            author=cls.tester,
            default_tester=None,
            reviewer=cls.tester,
            case_status=cls.case_status_confirmed,
            plan=[cls.plan])
        cls.case_1 = f.TestCaseFactory(
            summary='Test case 1',
            author=cls.tester,
            default_tester=None,
            reviewer=cls.tester,
            case_status=cls.case_status_confirmed,
            plan=[cls.plan])
        cls.case_2 = f.TestCaseFactory(
            summary='Test case 2',
            author=cls.tester,
            default_tester=None,
            reviewer=cls.tester,
            case_status=cls.case_status_confirmed,
            plan=[cls.plan])
        cls.case_3 = f.TestCaseFactory(
            summary='Test case 3',
            author=cls.tester,
            default_tester=None,
            reviewer=cls.tester,
            case_status=cls.case_status_confirmed,
            plan=[cls.plan])

        cls.case_4 = f.TestCaseFactory(
            summary='Test case 4',
            author=cls.tester,
            default_tester=None,
            reviewer=cls.tester,
            case_status=cls.case_status_confirmed,
            plan=[cls.plan])
        cls.case_5 = f.TestCaseFactory(
            summary='Test case 5',
            author=cls.tester,
            default_tester=None,
            reviewer=cls.tester,
            case_status=cls.case_status_confirmed,
            plan=[cls.plan])
        cls.case_6 = f.TestCaseFactory(
            summary='Test case 6',
            author=cls.tester,
            default_tester=None,
            reviewer=cls.tester,
            case_status=cls.case_status_confirmed,
            plan=[cls.plan])


class BaseCaseRun(BasePlanCase):
    """Base test case containing test run and case runs"""

    @classmethod
    def setUpTestData(cls):
        super().setUpTestData()

        cls.case_run_status_idle = TestCaseRunStatus.objects.get(name='IDLE')

        cls.build = f.TestBuildFactory(product=cls.product)

        cls.test_run = f.TestRunFactory(
            product_version=cls.version,
            plan=cls.plan,
            build=cls.build,
            manager=cls.tester,
            default_tester=cls.tester)

        cls.case_run_1, cls.case_run_2, cls.case_run_3 = [
            f.TestCaseRunFactory(
                assignee=cls.tester,
                tested_by=cls.tester,
                run=cls.test_run,
                build=cls.build,
                case_run_status=cls.case_run_status_idle,
                case=case,
                sortkey=i * 10)
            for i, case in enumerate((cls.case_1, cls.case_2, cls.case_3), 1)
        ]

        cls.test_run_1 = f.TestRunFactory(
            product_version=cls.version,
            plan=cls.plan,
            build=cls.build,
            manager=cls.tester,
            default_tester=cls.tester)

        cls.case_run_4, cls.case_run_5, cls.case_run_6 = [
            f.TestCaseRunFactory(
                assignee=cls.tester,
                tested_by=cls.tester,
                run=cls.test_run_1,
                build=cls.build,
                case_run_status=cls.case_run_status_idle,
                case=case,
                sortkey=i * 10)
            for i, case in enumerate((cls.case_4, cls.case_5, cls.case_6), 1)
        ]
