#!/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, os.pardir))

from saml2.metadata import entity_descriptor
from saml2.server import Server
from spid_sp_test import BASE_DIR, __version__
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


logger = logging.getLogger(__name__)


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


if __name__ == '__main__':
    _desc = (f'{__file__} -h for help')
    _epilog = f"""spid_sp_test {__version__} usage 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=https://localhost:8080

        # select which tests to execute
        {__file__} --metadata-url http://localhost:8000/spid/metadata --authn-url http://localhost:8000/spid/login/?idp=https://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=https://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=https://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=https://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=https://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=https://localhost:8080 --extra --debug ERROR -tr --response-html-dumps ./html

        # html report
        {__file__} --metadata-url http://localhost:8000/spid/metadata --authn-url http://localhost:8000/spid/login/?idp=https://localhost:8080 --extra --debug INFO --test-response -rf html -o html_report/

    """

    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='execute 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: 1 2 3"
    )

    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(
        '-o', '--report-output-file', required=False,
        help="the report will be dumped in a specified path, require  -rf {json,html} option"
    )

    parser.add_argument(
        '-rf', '--report_format',
        required=False,
        choices=['json', 'html'],
        help=(
            "Report format, json or html. "
            "If html the repo will be stored to the ./html folder by default. "
            "You can specificy another path using --report-output-file"
        )
    )

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

    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(
        '--response-html-dumps', '-rhd',
        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")
    )

    parser.add_argument(
        '-pr', '--profile', required=False,
        default='spid-sp-public',
        choices=(
            'saml2-sp',
            'spid-sp-public',
            'spid-sp-private',
            'spid-sp-ag-public-full',
            'spid-sp-ag-public-lite',
            'spid-sp-ag-private-full',
            'spid-sp-ag-private-lite',
            'spid-sp-op-public-full',
            'spid-sp-op-public-lite',
            'cie-sp-public',
            'cie-sp-private',
            'ficep-eidas-sp'
        ),
        help="which profile to check; defaults to spid-sp-public if omitted"
    )

    parser.add_argument(
        '-ap', '--authn-plugin',
        required=False,
        help="use a custom plugin for authn requests, eg: for proxies/gateways"
    )

    parser.add_argument(
        '-rm', '--request-method',
        required=False, default='GET',
        help="HTTP method to use when requesting authn-url e.g. GET or POST; defaults to GET if omitted"
    )

    parser.add_argument(
        '-rb', '--request-body',
        required=False, type=json.loads,
        help='key-value pairs payload to submit in the HTTP request body when requesting authn-url; e.g.: {"idp": "https://localhost:8080"}'
    )

    parser.add_argument(
        '-rct', '--request-content-type',
        required=False, default='data',
        help=("python requests' defaults arguments: data or json; "
              "use 'data' for application/x-www-form-urlencoded; "
              "use 'json' for application/json; defaults to data if omitted"
        )
    )

    parser.add_argument(
        '-prs', '--print-responses-settings',
        required=False, action="store_true",
        help='print out the default responses settings'
    )

    parser.add_argument(
        '-pas', '--print-attributes-settings',
        required=False, action="store_true",
        help='print out the default user attributes used in automatic responses'
    )

    parser.add_argument(
        '--xsds-files-path', '--xfp',
        required=False,
        help="path where xsd files are"
    )

    parser.add_argument(
        '-v', '--version',
        required=False, action="store_true",
        help='print out spid-sp-test version'
    )


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

    if len(sys.argv) == 1:
        parser.print_help(sys.stderr)
        sys.exit(1)
    elif args.version:
        print(__version__)
        sys.exit(0)

    profile = f"test_profile_{args.profile.replace('-', '_')}"

    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.print_responses_settings:
        from spid_sp_test.responses.settings import RESPONSE_TESTS
        print(json.dumps(RESPONSE_TESTS, indent=2))
        sys.exit(0)

    elif args.print_attributes_settings:
        from spid_sp_test.responses.settings import ATTRIBUTES
        print(json.dumps(ATTRIBUTES, indent=2))
        sys.exit(0)

    elif args.metadata_url:
        if args.extra:
            _cls = SpidSpMetadataCheckExtra
        else:
            _cls = SpidSpMetadataCheck
        data_md = dict(metadata_url=args.metadata_url,
                       production=args.production,
                       xsds_files_path=args.xsds_files_path)
        metadata_check = _cls(**data_md)
        try:
            metadata_check.load()
            selective_run(metadata_check, profile, args.list)
        except Exception as e:
            logger.critical(f"Errors occourred during Check, {_cls}: {e}")
            args.authn_url = 0
        tests_done.append(metadata_check)

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

    # authn request
    if args.authn_url:
        if args.extra:
            _cls = SpidSpAuthnReqCheckExtra
        else:
            _cls = SpidSpAuthnReqCheck
        data_ac = dict(
            metadata = metadata_check.metadata,
            authn_request_url = args.authn_url,
            production = args.production,
            authn_plugin = args.authn_plugin,
            request_method = args.request_method,
            request_body = args.request_body,
            request_content_type = args.request_content_type
        )
        authn_check = _cls(**data_ac)
        try:
            authn_check.load()
            selective_run(authn_check, profile, args.list)
        except Exception as e:
            logger.critical(f"Errors occourred during Check, {_cls}: {e}")
            args.test_response = 0
        tests_done.append(authn_check)

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

        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.response_html_dumps else None,
            no_send_response = args.no_send_response,
            authn_plugin = args.authn_plugin,
            requests_session = authn_check.authn_request['requests_session'],
            request_method = args.request_method,
            request_body = args.request_body,
            request_content_type = args.request_content_type,
            xsds_files_path=args.xsds_files_path
        )

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

    if args.response_html_dumps:
        print(f"\nSAML Response html dumps are at {args.response_html_dumps}\n")

    # OUTPUT - REPORT
    if args.report_output_file or args.report_format:
        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.report_output_file and args.report_format == 'json':
            with open(args.report_output_file, 'w') as f:
                f.write(output)
        elif args.report_format == 'html':
            output_folder = args.report_output_file or os.path.join('.', 'html')
            render_html_report(data['test']['sp'],
                               output_folder = output_folder,
                               display_name = args.metadata_url)
            print(f"\nHTML report is at {output_folder}\n")

        elif args.report_format == 'json':
            print(output)

    # check exit status
    tests = sum([len(i.results) for i in tests_done])
    tests_warnings = sum([len(i.warnings) 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. '
            f'{tests_warnings} warnings.'
        )

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