import json
from pathlib import Path
from urllib.parse import urldefrag, urlsplit, urlunsplit

import pytest

from fastjsonschema import RefResolver, JsonSchemaValueException, compile, _get_code_generator_class
from fastjsonschema.exceptions import JsonSchemaDefinitionException


SUITE_ROOT = Path(__file__).resolve().parents[2] / 'JSON-Schema-Test-Suite'
REMOTES_DIR = SUITE_ROOT / 'remotes'
REMOTES_BASE_URL = 'http://localhost:1234/'
METASCHEMAS_DIR = Path(__file__).resolve().parent / 'metaschemas'


def _build_suite_remotes():
    remotes = {}
    if not REMOTES_DIR.is_dir():
        return remotes
    for path in sorted(REMOTES_DIR.rglob('*.json')):
        rel = path.relative_to(REMOTES_DIR).as_posix()
        url = REMOTES_BASE_URL + rel
        with path.open(encoding='utf-8') as remote_file:
            remotes[url] = json.load(remote_file)
    return remotes


def _build_metaschemas():
    metaschemas = {}
    if not METASCHEMAS_DIR.is_dir():
        return metaschemas
    for path in sorted(METASCHEMAS_DIR.rglob('*.json')):
        with path.open(encoding='utf-8') as metaschema_file:
            schema = json.load(metaschema_file)
        for key in ('$id', 'id'):
            if key in schema and isinstance(schema[key], str):
                _register_metaschema_uri(metaschemas, schema[key], schema)
    return metaschemas


def _register_metaschema_uri(metaschemas, uri, schema):
    uri, _ = urldefrag(uri)
    metaschemas[uri] = schema
    if uri.startswith('https://json-schema.org/'):
        metaschemas[uri.replace('https://', 'http://', 1)] = schema
    elif uri.startswith('http://json-schema.org/'):
        metaschemas[uri.replace('http://', 'https://', 1)] = schema


def remotes_handler(uri):
    lookup_uri = _normalize_lookup_uri(uri)
    if lookup_uri in REMOTES:
        return REMOTES[lookup_uri]
    if lookup_uri in METASCHEMAS:
        return METASCHEMAS[lookup_uri]
    raise JsonSchemaDefinitionException('Unknown remote URI: {}'.format(uri))


def _normalize_lookup_uri(uri):
    parts = urlsplit(uri)
    if parts.scheme == 'https' and parts.netloc == 'localhost:1234':
        parts = parts._replace(scheme='http')
        return urlunsplit(parts)
    return uri


REMOTES = _build_suite_remotes()
METASCHEMAS = _build_metaschemas()
SCHEMA_HANDLERS = {'http': remotes_handler, 'https': remotes_handler}


def resolve_param_values_and_ids(schema_version, suite_dir, ignored_suite_files=[], ignore_tests=[]):
    suite_dir_path = Path(suite_dir).resolve()
    test_file_paths = sorted(set(suite_dir_path.glob("**/*.json")))

    param_values = []
    param_ids = []
    for test_file_path in test_file_paths:
        with test_file_path.open(encoding='UTF-8') as test_file:
            test_cases = json.load(test_file)
            for test_case in test_cases:
                for test_data in test_case['tests']:
                    param_values.append(pytest.param(
                        schema_version,
                        test_case['schema'],
                        test_data['data'],
                        test_data['valid'],
                        marks=pytest.mark.xfail
                            if test_file_path.name in ignored_suite_files
                                or test_case['description'] in ignore_tests
                            else pytest.mark.none,
                    ))
                    param_ids.append('{} / {} / {}'.format(
                        test_file_path.name,
                        test_case['description'],
                        test_data['description'],
                    ))
    return param_values, param_ids


def template_test(schema_version, schema, data, is_valid):
    """
    Test function to be used (imported) in final test file to run the tests
    which are generated by `pytest_generate_tests` hook.
    """
    # For debug purposes. When test fails, it will print stdout.
    resolver = RefResolver.from_schema(schema, handlers=SCHEMA_HANDLERS)

    debug_generator = _get_code_generator_class(schema_version)(schema, resolver=resolver)
    print(debug_generator.global_state_code)
    print(debug_generator.func_code)

    # JSON schema test suits do not contain schema version.
    # Our library needs to know that or it would use always the latest implementation.
    if isinstance(schema, dict):
        schema.setdefault('$schema', schema_version)

    validate = compile(schema, handlers=SCHEMA_HANDLERS)
    try:
        result = validate(data)
        print('Validate result:', result)
    except JsonSchemaValueException:
        if is_valid:
            raise
    else:
        if not is_valid:
            pytest.fail('Test should not pass')
