#!/usr/bin/env python3
import argparse
import json
import logging
import os
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir))

from saml2.metadata import entity_descriptor
from saml2.server import Server
from spid_sp_test import BASE_DIR
from spid_sp_test.metadata import SpidSpMetadataCheck
from spid_sp_test.metadata_extra import SpidSpMetadataCheckExtra
from spid_sp_test.authn_request import SpidSpAuthnReqCheck
from spid_sp_test.authn_request_extra import SpidSpAuthnReqCheckExtra
from spid_sp_test.html_report import render_html_report
from spid_sp_test.response import SpidSpResponseCheck
from spid_sp_test.utils import get_xmlsec1_bin, report_to_html


logger = logging.getLogger(__name__)


def selective_run(obj, meth_list):
    if meth_list:
        for method in meth_list:
            meth = getattr(obj, method, None)
            if meth:
                meth()
    else:
        obj.test_all()


if __name__ == '__main__':
    _desc = (f'{__file__} -h for help')
    _epilog = f"""examples:
        {__file__} --metadata-url file://metadata.xml
        {__file__} --metadata-url http://localhost:8000/spid/metadata --extra
        {__file__} --metadata-url http://localhost:8000/spid/metadata -l test_Organization test_Signature

        # export idp metadata
        {__file__} --idp-metadata

        # test an authentication request made by a SP
        {__file__} --metadata-url http://localhost:8000/spid/metadata --authn-url http://localhost:8000/spid/login/?idp=spid-idp-test

        # select which tests to execute
        {__file__} --metadata-url http://localhost:8000/spid/metadata --authn-url http://localhost:8000/spid/login/?idp=http://localhost:8080 --extra -debug ERROR -json -l xsd_check

        # execute Response tests
        {__file__} --metadata-url http://localhost:8000/spid/metadata --authn-url http://localhost:8000/spid/login/?idp=http://localhost:8080 --extra -debug ERROR -tr

        # select which response test to execute
        {__file__} --metadata-url http://localhost:8000/spid/metadata --authn-url http://localhost:8000/spid/login/?idp=http://localhost:8080 --extra --debug INFO -tr -tn 1 8 9 24 63

        # run a test suite configured in a json file
        {__file__} --metadata-url http://localhost:8000/spid/metadata --authn-url http://localhost:8000/spid/login/?idp=http://localhost:8080 --extra --debug INFO -tr -tj tests/example.test-suite.json

        # select which user attribute to return in response via json file
        {__file__} --metadata-url http://localhost:8000/spid/metadata --authn-url http://localhost:8000/spid/login/?idp=http://localhost:8080 --extra --debug DEBUG -aj tests/example.attributes.json

        # dump SP response as html page
        {__file__} --metadata-url http://localhost:8000/spid/metadata --authn-url http://localhost:8000/spid/login/?idp=http://localhost:8080 --extra --debug ERROR -tr --html-path dumps
    """

    parser = argparse.ArgumentParser(
        description=_desc,
        epilog=_epilog,
        formatter_class=argparse.RawTextHelpFormatter
    )
    parser.add_argument(
        '--metadata-url', required=False, default=None,
        help=("URL where SAML2 Metadata resides: "
              "it can be file://path or https://fqdn")
    )

    parser.add_argument(
        '--idp-metadata',
        action="store_true", default=False,
        help="get example IdP metadata"
    )

    parser.add_argument(
        '-l', '--list', nargs='*',
        help='esecute only selected checks',
        required=False
    )

    parser.add_argument(
        '--extra',
        action="store_true", default=False,
        help="execute extra checks"
    )

    parser.add_argument(
        '--authn-url', required=False, default=None,
        help=("URL where the SP initializes "
              "the Authentication Request to this IDP,"
              "it can also be a file:///")
    )

    parser.add_argument(
        '-tr', '--test-response',
        action="store_true", default=False,
        help="execute SAML2 responses"
    )

    parser.add_argument(
        '-nsr', '--no-send-response',
        action="store_true", default=False,
        help = ("print SAML2 Response without sending back to SP. "
                "It only works with '-tr'")
    )

    parser.add_argument(
        '-tp', '--template-path',
        default=f'{BASE_DIR}/responses/templates/',
        required=False,
        help=("templates containing SAML2 xml "
              "templates for response tests")
    )

    parser.add_argument(
        '-tn', '--test-names',
        action='append', nargs='*',
        required=False,
        help="response test to be executed, eg: 01 02 03"
    )

    parser.add_argument(
        '-tj', '--test-jsons',
        action='append', nargs='*',
        required=False,
        help=("custom test via json file, "
              "eg: tests/example.test-suite.json")
    )

    parser.add_argument(
        '-aj', '--attr-json',
        required=False,
        help="loads user attributes via json, eg: tests/example.attributes.json"
    )

    parser.add_argument(
        '-report', action="store_true",
        help="json report in stdout"
    )

    parser.add_argument(
        '-o', required=False,
        help="report to file, -report is required"
    )

    parser.add_argument(
        '-rf', '--report_format',
        required=False,
        default='json',
        choices=['json', 'html'],
        help="Report format, json or pdf"
    )

    parser.add_argument(
        '-d', '--debug', required=False,
        choices=('CRITICAL', 'ERROR',
                 'WARNING', 'INFO', 'DEBUG'),
        default='INFO',
        help="Debug level, see python logging"
    )

    parser.add_argument(
        '-xp', '--xmlsec-path',
        default=get_xmlsec1_bin(),
        required=False,
        help="xmlsec1 executable path, eg: /usr/bin/xmlsec1"
    )

    parser.add_argument(
        '--production', '-p',
                        action="store_true",
                        default=False,
                        help=("execute tests for system in production, "
                              "eg: https and TLS quality")
    )

    parser.add_argument(
        '--html-path', '-hp',
        help=("Only works with Response tests activated. "
              "Path where the html response pages will "
              "be dumped after by the SP")
    )

    parser.add_argument(
        '--exit-zero', '-ez',
        action="store_true",
        default=False,
        help=("exit with 0 even if tests fails")
    )

    args = parser.parse_args()

    logging.basicConfig(level=getattr(logging, args.debug))

    if len(sys.argv) == 1:
        parser.print_help(sys.stderr)
        sys.exit(1)

    tests_done = []
    if args.idp_metadata:
        from spid_sp_test.idp.settings import SAML2_IDP_CONFIG

        idp_server = Server(SAML2_IDP_CONFIG)
        idp_metadata = entity_descriptor(idp_server.config)
        print(idp_metadata.to_string().decode())
        sys.exit(0)

    elif args.metadata_url:
        data_md = dict(metadata_url=args.metadata_url,
                       production=args.production)
        metadata_check = SpidSpMetadataCheck(**data_md)
        selective_run(metadata_check, args.list)
        tests_done.append(metadata_check)
        if args.extra:
            metadata_check_extra = SpidSpMetadataCheckExtra(**data_md)
            selective_run(metadata_check_extra, args.list)
            tests_done.append(metadata_check_extra)

    else:
        logging.error('At least --idp-metadata or --metadata-url is needed!')
        sys.exit(1)

    # authn request
    if args.authn_url:
        data_ac = dict(
            metadata=metadata_check.metadata,
            authn_request_url=args.authn_url,
            production=args.production
        )
        authn_check = SpidSpAuthnReqCheck(**data_ac)
        selective_run(authn_check, args.list)
        tests_done.append(authn_check)

        if args.extra:
            authn_check_extra = SpidSpAuthnReqCheckExtra(**data_ac)
            selective_run(authn_check_extra, args.list)
            tests_done.append(authn_check_extra)

    # Responses
    if args.test_response:
        # 'html-dumps_{datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}'
        html_path = f'{args.html_path}/'

        data_tr = dict(
            metadata_etree=metadata_check.doc,
            authn_request_url=args.authn_url,
            relay_state=authn_check.relay_state,
            template_path=args.template_path,
            xmlsec_binary=args.xmlsec_path,
            test_names=args.test_names[0] if args.test_names else [],
            test_jsons=args.test_jsons or [],
            attr_json=args.attr_json,
            production=args.production,
            html_path=html_path if args.html_path else None,
            no_send_response = args.no_send_response
        )

        if args.html_path:
            os.mkdir(html_path)

        response_check = SpidSpResponseCheck(**data_tr)
        selective_run(response_check, args.list)
        tests_done.append(response_check)

    # OUTPUT - REPORT
    if args.o or args.report:
        data = {
            "test": {
                "sp": {}
            }
        }

        for i in tests_done:
            data['test']['sp'][i.category] = i.report_to_dict()[i.category]

        output = json.dumps(data, indent=2)
        if args.o:
            if args.report_format == 'json':
                with open(args.o, 'w') as f:
                    f.write(output)
            elif args.report_format == 'html':
                render_html_report(data['test']['sp'],
                                   output_folder = args.o,
                                   display_name = args.metadata_url)

        if args.report and not args.o:
            print(output)

    # check exit status
    tests = sum([len(i.results) for i in tests_done])
    tests_errors = sum([len(i.errors) for i in tests_done])

    if not args.no_send_response:
        print(f'Spid QA: executed {tests} tests, {tests_errors} failed.')

    if tests_errors and not args.exit_zero:
        sys.exit(1)
