"""
Maps py-agent's TestedCodeDTO → GenerateTestRequestDataDto format
expected by the Early backend /api/v1/tests/generate-prompt endpoint.
"""

import logging
import uuid
from typing import Any

from py_agent.models.class_metadata import ClassMetadata
from py_agent.models.method_metadata import MethodMetadata
from py_agent.models.parameter import Parameter
from py_agent.models.tested_code import TestedCodeDTO

logger = logging.getLogger(__name__)


def _map_parameter(param: Parameter) -> dict[str, Any]:
    return {
        "id": str(param.id),
        "name": param.name,
        "types": [{"name": t.name, "kind": t.kind} for t in (param.types or [])],
        "isOptional": param.is_optional,
        "isCollection": param.is_collection,
    }


def _map_class_metadata(cm: ClassMetadata) -> dict[str, Any]:
    result: dict[str, Any] = {
        "name": cm.name,
        "code": cm.code or "",
        "isAbstract": cm.is_abstract,
        "properties": [],
        "types": [],
        "interfacesImplemented": [],
        "isDeprecated": False,
        "decorators": [],
    }
    if cm.constructor_metadata:
        result["constructorMetadata"] = {
            "code": cm.constructor_metadata.code or "",
            "parameters": [_map_parameter(p) for p in (cm.constructor_metadata.parameters or [])],
        }
    return result


def _map_method_metadata(mm: MethodMetadata) -> dict[str, Any]:
    result: dict[str, Any] = {
        "name": mm.name,
        "code": mm.code or "",
        "isAsync": mm.is_async,
        "isStatic": mm.is_static,
        "parameters": [_map_parameter(p) for p in (mm.parameters or [])],
        "imports": [],
        "decorators": [],
        "signature": mm.signature or "",
    }
    if mm.return_parameter:
        result["returnParam"] = _map_parameter(mm.return_parameter)
    return result


def map_tested_code_to_request_dto(
    tested_code: TestedCodeDTO,
    relative_test_file_path: str,
    test_framework: str = "pytest",
    client_source: str = "cli",
    user_prompt: str = "",
    git_url: str | None = None,
    metadata_collection_time: int | None = None,
    workflow_run_id: str | None = None,
) -> dict[str, Any]:
    extracted = tested_code.extracted_data

    parameters: list[dict[str, Any]] = []
    return_param = None
    is_async = False
    is_static = False
    function_code = ""
    class_metadata_dto = None

    if extracted:
        function_code = extracted.function_code
        is_async = extracted.is_async
        is_static = extracted.is_static
        parameters = [_map_parameter(p) for p in (extracted.parameters or [])]
        if extracted.return_parameter:
            return_param = _map_parameter(extracted.return_parameter)
        if extracted.class_metadata:
            class_metadata_dto = _map_class_metadata(extracted.class_metadata)

    missing_deps: list[dict[str, Any]] = []
    code_deps: list[dict[str, Any]] = []

    if tested_code.dependencies:
        for dep in tested_code.dependencies.missing_dependencies or []:
            dep_dict: dict[str, Any] = {
                "name": dep.name,
                "kind": dep.kind,
                "importPath": dep.import_path,
                "isCoreModule": dep.is_core_module,
                "containedInTestedFile": dep.contained_in_tested_file,
                "isDataObject": dep.is_data_object,
                "parentParameterIds": [],
                "parentParameterRelations": [],
            }
            if dep.code:
                dep_dict["code"] = dep.code
            if dep.dependency_metadata:
                dm = dep.dependency_metadata
                mock_dep: dict[str, Any] = {"methodsMetadata": []}
                if dm.class_metadata:
                    mock_dep["classMetadata"] = _map_class_metadata(dm.class_metadata)
                mock_dep["methodsMetadata"] = [
                    _map_method_metadata(mm) for mm in (dm.methods_metadata or [])
                ]
                dep_dict["dependencyMetadata"] = mock_dep
            missing_deps.append(dep_dict)

        for code_dep in tested_code.dependencies.code_dependencies or []:
            code_deps.append(
                {
                    "name": code_dep.name,
                    "kind": code_dep.kind,
                    "code": code_dep.code or "",
                    "importPath": code_dep.import_path,
                    "containedInTestedFile": code_dep.contained_in_tested_file,
                    "isDataObject": code_dep.is_data_object,
                    "parentParameterRelations": [
                        {
                            "parameterParentId": str(r.parameter_parent_id),
                            "depthLevelInParent": r.depth_level_in_parent,
                        }
                        for r in (code_dep.parent_parameter_relations or [])
                    ],
                }
            )

    tested_code_data_source: dict[str, Any] = {
        "gitUrl": git_url,
        "filePath": tested_code.file_path,
        "language": "python",
        "testFramework": test_framework,
        "relativePathToTestFile": relative_test_file_path,
        "usages": [],
        "libraries": [],
        "testedMethod": {
            "name": tested_code.function_name,
            "code": function_code,
            "isAsync": is_async,
            "isStatic": is_static,
            "parameters": parameters,
            "imports": [],
            "decorators": [],
            "signature": "",
            "kind": "function",
            "exportType": "not-exported",
            "accessModifierType": "public",
            "parameterId": str(uuid.uuid4()),
            **({"returnParam": return_param} if return_param is not None else {}),
        },
        **({"testedMethodClass": class_metadata_dto} if class_metadata_dto is not None else {}),
        "missingDependencies": missing_deps,
        "codeDependencies": code_deps,
    }
    if metadata_collection_time is not None:
        tested_code_data_source["metadataCollectionTime"] = metadata_collection_time

    result: dict[str, Any] = {
        "testedCodeDataSource": tested_code_data_source,
        "llmConfig": {},
        "clientSource": client_source,
        "userPrompt": user_prompt,
    }
    if workflow_run_id:
        result["githubActionWorkflowRunId"] = workflow_run_id
    logger.info(
        f"workflow_run_id={workflow_run_id!r} → githubActionWorkflowRunId in payload: {workflow_run_id is not None and len(workflow_run_id) > 0 if workflow_run_id else False}"
    )
    return result
