import itertools
from Levenshtein import jaro  # type: ignore
from normality import normalize
import fingerprints
from followthemoney.types import registry
from followthemoney.util import dampen, shortest
from followthemoney.exc import InvalidData

# OK, Here's the plan: we have to find a way to get user judgements
# on as many of these matches as we can, then build a regression
# model which properly weights the value of a matching property
# based upon it's type.
NAMES_WEIGHT = 0.6
COUNTRIES_WEIGHT = 0.1
MATCH_WEIGHTS = {
    registry.text: 0,
    registry.name: 0,  # because we already compare names
    registry.identifier: 0.3,
    registry.url: 0.1,
    registry.email: 0.2,
    registry.ip: 0.1,
    registry.iban: 0.3,
    registry.address: 0.3,
    registry.date: 0.2,
    registry.phone: 0.3,
    registry.country: 0.1,
}


def compare(model, left, right):
    """Compare two entities and return a match score."""
    left = model.get_proxy(left)
    right = model.get_proxy(right)
    if right.schema not in list(left.schema.matchable_schemata):
        return 0
    schema = model.common_schema(left.schema, right.schema)
    score = compare_names(left, right) * NAMES_WEIGHT
    score += compare_countries(left, right) * COUNTRIES_WEIGHT
    for name, prop in schema.properties.items():
        weight = MATCH_WEIGHTS.get(prop.type, 0)
        if weight == 0 or not prop.matchable:
            continue
        try:
            left_values = left.get(name)
            right_values = right.get(name)
        except InvalidData:
            continue

        if not len(left_values) or not len(right_values):
            continue
        prop_score = prop.type.compare_sets(left_values, right_values)
        score += prop_score * weight
    return score


def _normalize_names(names):
    """Generate a sequence of comparable names for an entity. This also
    generates a `fingerprint`, i.e. a version of the name where all tokens
    are sorted alphabetically, and some parts, such as company suffixes,
    have been removed."""
    seen = set()
    for name in names:
        plain = normalize(name, ascii=True)
        if plain is not None and plain not in seen:
            seen.add(plain)
            yield plain
        fp = fingerprints.generate(name)
        if fp is not None and len(fp) > 6 and fp not in seen:
            seen.add(fp)
            yield fp


def compare_names(left, right):
    result = 0
    left_list = list(_normalize_names(left.names))
    right_list = list(_normalize_names(right.names))
    for (left, right) in itertools.product(left_list, right_list):
        similarity = jaro(left, right)
        score = similarity * dampen(2, 20, shortest(left, right))
        result = max(result, score)
    return result


def compare_countries(left, right):
    left = left.country_hints
    right = right.country_hints
    overlap = left.intersection(right)
    return min(2.0, len(overlap))
