"""Candidate generation (blocking) — reduces O(n*m) to tractable candidate sets."""

import logging
import re
from collections import defaultdict
from dataclasses import dataclass

logger = logging.getLogger(__name__)


@dataclass
class BlockingCandidate:
    """A candidate pair generated by blocking."""

    record_a_id: str
    record_a_source: str
    record_b_id: str
    record_b_source: str
    blocking_strategy: str


def normalize_email_domain(email: str) -> str | None:
    if not email or "@" not in email:
        return None
    return email.strip().lower().split("@")[1]


def normalize_company_name(name: str) -> str:
    if not name:
        return ""
    name = name.strip().lower()
    for suffix in [
        " inc",
        " inc.",
        " incorporated",
        " llc",
        " l.l.c.",
        " ltd",
        " ltd.",
        " corp",
        " corp.",
        " corporation",
        " co",
        " co.",
        " company",
        " gmbh",
        " ag",
        " sa",
        " bv",
    ]:
        if name.endswith(suffix):
            name = name[: -len(suffix)]
    name = re.sub(r"[^a-z0-9\s]", "", name)
    name = re.sub(r"\s+", " ", name).strip()
    return name


def normalize_phone(phone: str) -> str:
    digits = re.sub(r"\D", "", phone)
    return digits[-7:] if len(digits) >= 7 else digits


class BlockingEngine:
    """Generate candidate pairs by applying layered blocking strategies."""

    def generate_candidates(
        self, records_a: list[dict], source_a: str, records_b: list[dict], source_b: str
    ) -> list[BlockingCandidate]:
        candidates: set[tuple[str, str, str, str]] = set()
        candidates.update(self._block_by_email_domain(records_a, source_a, records_b, source_b))
        candidates.update(self._block_by_company_name(records_a, source_a, records_b, source_b))
        candidates.update(self._block_by_phone(records_a, source_a, records_b, source_b))

        logger.info("Generated %d blocking candidates (%s vs %s)", len(candidates), source_a, source_b)
        return [BlockingCandidate(a_id, a_src, b_id, b_src, "union") for a_id, a_src, b_id, b_src in candidates]

    def _block_by_field(
        self,
        records_a: list[dict],
        source_a: str,
        records_b: list[dict],
        source_b: str,
        field_names: list[str],
        normalize_fn,
    ) -> set[tuple[str, str, str, str]]:
        index: dict[str, list[tuple[str, str]]] = defaultdict(list)
        candidates: set[tuple[str, str, str, str]] = set()

        for rec in records_a:
            for field in field_names:
                val = rec.get("data", {}).get(field) or rec.get(field)
                if val:
                    key = normalize_fn(str(val))
                    if key:
                        index[key].append((rec.get("record_id", ""), source_a))

        for rec in records_b:
            for field in field_names:
                val = rec.get("data", {}).get(field) or rec.get(field)
                if val:
                    key = normalize_fn(str(val))
                    if key and key in index:
                        rec_id = rec.get("record_id", "")
                        for a_id, a_src in index[key]:
                            candidates.add((a_id, a_src, rec_id, source_b))
        return candidates

    def _block_by_email_domain(self, records_a, source_a, records_b, source_b):
        return self._block_by_field(
            records_a,
            source_a,
            records_b,
            source_b,
            ["email", "email_address", "owner_email", "contact_email"],
            lambda e: normalize_email_domain(e),
        )

    def _block_by_company_name(self, records_a, source_a, records_b, source_b):
        return self._block_by_field(
            records_a,
            source_a,
            records_b,
            source_b,
            ["name", "company_name", "company", "account_name", "organization"],
            lambda n: normalize_company_name(n) if len(normalize_company_name(n)) >= 2 else None,
        )

    def _block_by_phone(self, records_a, source_a, records_b, source_b):
        return self._block_by_field(
            records_a,
            source_a,
            records_b,
            source_b,
            ["phone", "phone_number", "mobile", "tel"],
            lambda p: normalize_phone(p) if len(normalize_phone(p)) >= 7 else None,
        )
