import logging
from pathlib import Path
from typing import Unpack, cast

from pydantic import BaseModel, StrictBool

from labels.model.core import (
    OutputFormat,
    SbomConfig,
    ScanArgs,
    SourceType,
)
from labels.model.sources import AwsCredentials
from labels.utils.exceptions import InvalidConfigFileError
from labels.utils.yaml import load_yaml_mapping

LOGGER = logging.getLogger(__name__)


class RawOutputConfig(BaseModel):
    name: str
    format: OutputFormat


class RawDockerCredentials(BaseModel):
    username: str
    password: str


class RawPlatformOverrides(BaseModel):
    os_override: str | None = None
    arch_override: str | None = None


class RawAwsCredentials(BaseModel):
    external_id: str | None = None
    role: str | None = None
    region: str | None = None
    access_key_id: str | None = None
    secret_access_key: str | None = None
    session_token: str | None = None


class RawConfig(BaseModel):
    model_config = {"extra": "allow"}

    source: str
    source_type: SourceType
    output: RawOutputConfig
    include: list[str] = ["."]
    exclude: list[str] = []
    execution_id: str | None = None
    docker_credentials: RawDockerCredentials | None = None
    platform_overrides: RawPlatformOverrides | None = None
    aws_credentials: RawAwsCredentials | None = None
    debug: StrictBool = False
    include_package_metadata: StrictBool = True
    include_package_advisories: StrictBool = True


class RawSbomSubConfig(BaseModel):
    output: RawOutputConfig
    include: list[str] = ["."]
    exclude: list[str] = []
    include_package_metadata: StrictBool = True
    include_package_advisories: StrictBool = True


class RawStaticScanConfig(BaseModel):
    model_config = {"extra": "allow"}

    sbom: RawSbomSubConfig
    source_type: SourceType = SourceType.DIRECTORY
    working_dir: str = "./"
    source: str | None = None
    execution_id: str | None = None
    docker_credentials: RawDockerCredentials | None = None
    platform_overrides: RawPlatformOverrides | None = None
    aws_credentials: RawAwsCredentials | None = None
    debug: StrictBool = False


def build_labels_config_from_args(arg: str, **kwargs: Unpack[ScanArgs]) -> SbomConfig:
    return SbomConfig(
        source=arg,
        source_type=SourceType.from_string(kwargs["source"]),
        execution_id=None,
        output_format=OutputFormat.from_string(kwargs["format"]),
        output=kwargs["output"],
        docker_user=kwargs["docker_user"],
        docker_password=kwargs["docker_password"],
        os_override=kwargs["os"],
        arch_override=kwargs["arch"],
        aws_external_id=kwargs["aws_external_id"],
        aws_role=kwargs["aws_role"],
        aws_region=kwargs["aws_region"],
        feature_preview=kwargs["feature_preview"],
        debug=kwargs["debug"],
    )


def build_labels_config_from_file(
    config_file_path: str,
    *,
    static_scan: bool = False,
) -> SbomConfig:
    if Path(config_file_path).is_file():
        if not config_file_path.endswith((".yaml", ".yml")):
            error_msg = "The configuration file must be a YAML format"
            raise InvalidConfigFileError(config_file_path, error_msg)
        if static_scan:
            return load_config_from_static_scan(config_file_path)
        return load_config_from_file(config_file_path)

    raise InvalidConfigFileError(config_file_path)


def _load_aws_credentials(
    raw_aws: RawAwsCredentials | None,
) -> AwsCredentials | None:
    if raw_aws and raw_aws.access_key_id is not None and raw_aws.secret_access_key is not None:
        return AwsCredentials(
            access_key_id=raw_aws.access_key_id,
            secret_access_key=raw_aws.secret_access_key,
            session_token=raw_aws.session_token,
        )
    return None


def _build_raw_config(config_path: str) -> RawConfig:
    raw_data = load_yaml_mapping(config_path)
    config = RawConfig.model_validate(raw_data)

    extras = cast("dict[str, object] | None", config.model_extra)
    if extras:
        _log_unrecognized_keys(extras)
    return config


def _build_raw_static_scan_config(config_path: str) -> RawStaticScanConfig:
    raw_data = load_yaml_mapping(config_path)
    config = RawStaticScanConfig.model_validate(raw_data)

    extras = cast("dict[str, object] | None", config.model_extra)
    if extras:
        _log_unrecognized_keys(extras)
    return config


def _log_unrecognized_keys(extras: dict[str, object]) -> None:
    unrecognized_keys = ", ".join(sorted(extras))
    msg = (
        f"Some keys were not recognized: {unrecognized_keys}."
        " The analysis will be performed only using the supported keys"
        " and defaults."
    )
    LOGGER.warning(msg)


def load_config_from_file(config_path: str) -> SbomConfig:
    config = _build_raw_config(config_path)

    docker = config.docker_credentials
    aws = config.aws_credentials
    overrides = config.platform_overrides

    return SbomConfig(
        source=config.source,
        source_type=config.source_type,
        output_format=config.output.format,
        output=config.output.name,
        include=tuple(config.include),
        exclude=tuple(config.exclude),
        docker_user=docker.username if docker else None,
        docker_password=docker.password if docker else None,
        os_override=overrides.os_override if overrides else None,
        arch_override=overrides.arch_override if overrides else None,
        aws_external_id=aws.external_id if aws else None,
        aws_role=aws.role if aws else None,
        aws_region=aws.region if aws else None,
        aws_credentials=_load_aws_credentials(aws),
        execution_id=config.execution_id,
        debug=config.debug,
        include_package_metadata=config.include_package_metadata,
        include_package_advisories=config.include_package_advisories,
    )


def load_config_from_static_scan(config_path: str) -> SbomConfig:
    config = _build_raw_static_scan_config(config_path)
    sbom = config.sbom

    docker = config.docker_credentials
    aws = config.aws_credentials
    overrides = config.platform_overrides

    source = (
        config.working_dir
        if config.source_type == SourceType.DIRECTORY or config.source is None
        else config.source
    )

    return SbomConfig(
        source=source,
        source_type=config.source_type,
        output_format=sbom.output.format,
        output=sbom.output.name,
        include=tuple(sbom.include),
        exclude=tuple(sbom.exclude),
        docker_user=docker.username if docker else None,
        docker_password=docker.password if docker else None,
        os_override=overrides.os_override if overrides else None,
        arch_override=overrides.arch_override if overrides else None,
        aws_external_id=aws.external_id if aws else None,
        aws_role=aws.role if aws else None,
        aws_region=aws.region if aws else None,
        aws_credentials=_load_aws_credentials(aws),
        execution_id=config.execution_id,
        debug=config.debug,
        include_package_metadata=sbom.include_package_metadata,
        include_package_advisories=sbom.include_package_advisories,
    )
