import collections.abc
import json
import logging
import os
from dataclasses import Field, dataclass
from pathlib import Path
from textwrap import indent
from typing import ClassVar, Dict, Optional

from pants.backend.python.dependency_inference.default_module_mapping import DEFAULT_MODULE_PATTERN_MAPPING
from pants.backend.python.dependency_inference.module_mapper import AllPythonTargets
from pants.backend.python.target_types import PythonResolveField, PythonSourceField
from pants.base.specs import RawSpecs, RecursiveGlobSpec
from pants.engine.addresses import Address
from pants.engine.internals.synthetic_targets import SyntheticAddressMaps, SyntheticTargetsRequest
from pants.engine.internals.target_adaptor import TargetAdaptor
from pants.engine.rules import Get, collect_rules, rule
from pants.engine.target import (
    AllTargets,
    Dependencies,
    DictStringToStringField,
    FieldSet,
    InferDependenciesRequest,
    InferredDependencies,
    InvalidFieldTypeException,
    NestedDictStringToStringField,
    StringSequenceField,
    Target,
    Targets,
)
from pants.engine.unions import UnionRule
from pants.util.frozendict import FrozenDict

logger = logging.getLogger(__name__)


class GlobTargetTypes(NestedDictStringToStringField):
    alias = "target_types"


class GlobDependencies(Dependencies):
    pass


class GlobTarget(Target):
    alias = "glob"
    help = "Depends on all targets in the subtree recursively."
    core_fields = (
        GlobTargetTypes,
        GlobDependencies,
    )


@dataclass(frozen=True)
class GlobDependenciesInferenceFieldSet(FieldSet):
    required_fields = (
        GlobTargetTypes,
        GlobDependencies,
    )

    target_types: GlobTargetTypes
    dependencies: GlobDependencies


class InferGlobDependenciesRequest(InferDependenciesRequest[GlobDependenciesInferenceFieldSet]):
    infer_from = GlobDependenciesInferenceFieldSet


@rule(desc="Recursively add all targets as dependencies for the glob target.")
async def infer_glob_dependencies(
    request: InferGlobDependenciesRequest,
    all_targets: AllTargets,
) -> InferredDependencies:
    spec_path = request.field_set.address.spec_path
    target_types = request.field_set.target_types.value or {}

    dependencies = []
    for t in all_targets:
        if not t.alias in target_types:
            continue

        if not (
            t.address.spec_path == spec_path
            or t.address.spec_path.startswith(spec_path)
            and t.address.spec_path[len(spec_path)] == "/"
        ):
            continue

        logger.info("glob: check target %s", t.alias)
        if not all(
            (value := target_types[t.alias].get(field_cls.alias)) is None or value == field.value
            for field_cls, field in t.field_values.items()
        ):
            logger.info("glob: skip target %s, field values %s", t.alias, t.field_values)
            continue

        logger.info("glob: add target %s", t.alias)
        dependencies.append(t.address)

    return InferredDependencies(dependencies)


def target_types():
    return [GlobTarget]


def rules():
    return (
        *collect_rules(),
        UnionRule(InferDependenciesRequest, InferGlobDependenciesRequest),
    )
