from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Literal

from moto.core.common_models import BaseModel
from moto.moto_api._internal import mock_random


def prune_unset(value: Any) -> Any:
    """Recursively drop unset fields (None / empty dict / empty list).

    AWS Transfer omits optional fields that were never set from its Describe*
    responses rather than returning them as null, so the serialized model must
    do the same.
    """
    if isinstance(value, dict):
        pruned = {}
        for key, val in value.items():
            cleaned = prune_unset(val)
            if cleaned is not None and cleaned != {} and cleaned != []:
                pruned[key] = cleaned
        return pruned
    if isinstance(value, list):
        return [prune_unset(item) for item in value]
    return value


class UserHomeDirectoryType(str, Enum):
    PATH = "PATH"
    LOGICAL = "LOGICAL"


class UserHomeDirectoryMappingType(str, Enum):
    FILE = "FILE"
    DIRECTORY = "DIRECTORY"


@dataclass
class User(BaseModel):
    region_name: str
    account_id: str
    server_id: str
    home_directory: str | None
    home_directory_type: UserHomeDirectoryType | None
    policy: str | None
    role: str
    user_name: str
    arn: str = field(default="", init=False)
    home_directory_mappings: list[dict[str, str | None]] = field(default_factory=list)
    posix_profile: dict[str, str | list[str] | None] = field(default_factory=dict)
    ssh_public_keys: list[dict[str, str]] = field(default_factory=list)
    tags: list[dict[str, str]] = field(default_factory=list)

    def __post_init__(self) -> None:
        if self.arn == "":
            self.arn = f"arn:aws:transfer:{self.region_name}:{self.account_id}:user/{self.server_id}/{self.user_name}"

    def to_dict(self) -> dict[str, Any]:
        user = {
            "HomeDirectory": self.home_directory,
            "HomeDirectoryType": self.home_directory_type,
            "Policy": self.policy,
            "Role": self.role,
            "UserName": self.user_name,
            "Arn": self.arn,
            "HomeDirectoryMappings": [
                {
                    "Entry": mapping.get("entry"),
                    "Target": mapping.get("target"),
                    "Type": mapping.get("type"),
                }
                for mapping in self.home_directory_mappings
            ],
            "SshPublicKeys": [
                {
                    "DateImported": key.get("date_imported"),
                    "SshPublicKeyBody": key.get("ssh_public_key_body"),
                    "SshPublicKeyId": key.get("ssh_public_key_id"),
                }
                for key in self.ssh_public_keys
            ],
            "PosixProfile": {
                "Uid": self.posix_profile.get("uid"),
                "Gid": self.posix_profile.get("gid"),
                "SecondaryGids": self.posix_profile.get("secondary_gids"),
            },
            "Tags": self.tags,
        }

        return user


class ServerDomain(str, Enum):
    S3 = "S3"
    EFS = "EFS"


class ServerEndpointType(str, Enum):
    PUBLIC = "PUBLIC"
    VPC = "VPC"
    VPC_ENDPOINT = "VPC_ENDPOINT"


class ServerIdentityProviderSftpAuthenticationMethods(str, Enum):
    PASSWORD = "PASSWORD"
    PUBLIC_KEY = "PUBLIC_KEY"
    PUBLIC_KEY_OR_PASSWORD = "PUBLIC_KEY_OR_PASSWORD"
    PUBLIC_KEY_AND_PASSWORD = "PUBLIC_KEY_AND_PASSWORD"


class ServerIdentityProviderType(str, Enum):
    SERVICE_MANAGED = "SERVICE_MANAGED"
    API_GATEWAY = "API_GATEWAY"
    AWS_DIRECTORY_SERVICE = "AWS_DIRECTORY_SERVICE"
    AWS_LAMBDA = "AWS_LAMBDA"


class ServerProtocols(str, Enum):
    SFTP = "SFTP"
    FTP = "FTP"
    FTPS = "FTPS"
    AS2 = "AS2"


class ServerState(str, Enum):
    OFFLINE = "OFFLINE"
    ONLINE = "ONLINE"
    STARTING = "STARTING"
    STOPPING = "STOPPING"
    START_FAILED = "START_FAILED"
    STOP_FAILED = "STOP_FAILED"


AS2_TRANSPORTS_TYPE = list[Literal["HTTP"]]


@dataclass
class Server(BaseModel):
    region_name: str
    account_id: str
    certificate: str | None
    domain: ServerDomain | None
    endpoint_type: ServerEndpointType | None
    host_key_fingerprint: str | None
    identity_provider_type: ServerIdentityProviderType | None
    logging_role: str | None
    post_authentication_login_banner: str | None
    pre_authentication_login_banner: str | None
    protocols: list[ServerProtocols] | None
    security_policy_name: str | None
    structured_log_destinations: list[str] | None
    arn: str = field(default="", init=False)
    as2_service_managed_egress_ip_addresses: list[str] = field(default_factory=list)
    endpoint_details: dict[str, str] = field(default_factory=dict)
    identity_provider_details: dict[str, str] = field(default_factory=dict)
    protocol_details: dict[str, str] = field(default_factory=dict)
    s3_storage_options: dict[str, str | None] = field(default_factory=dict)
    server_id: str = field(default="", init=False)
    state: ServerState | None = ServerState.ONLINE
    tags: list[dict[str, str]] = field(default_factory=list)
    user_count: int = field(default=0)
    workflow_details: dict[str, list[dict[str, str]]] = field(default_factory=dict)
    _users: list[User] = field(default_factory=list, repr=False)

    def __post_init__(self) -> None:
        if self.server_id == "":
            self.server_id = f"s-{mock_random.get_random_hex(17)}"
        if self.arn == "":
            self.arn = f"arn:aws:transfer:{self.region_name}:{self.account_id}:server/{self.server_id}"
        if self.as2_service_managed_egress_ip_addresses == []:
            self.as2_service_managed_egress_ip_addresses.append("0.0.0.0/0")

    def to_dict(self) -> dict[str, Any]:
        on_upload = []
        on_partial_upload = []
        if self.workflow_details is not None:
            on_upload_workflows = self.workflow_details.get("on_upload")

            if on_upload_workflows is not None:
                for workflow in on_upload_workflows:
                    workflow_id = workflow.get("workflow_id")
                    execution_role = workflow.get("execution_role")
                    if workflow_id and execution_role:
                        on_upload.append(
                            {"WorkflowId": workflow_id, "ExecutionRole": execution_role}
                        )
            on_partial_upload_workflows = self.workflow_details.get("on_partial_upload")
            if on_partial_upload_workflows is not None:
                for workflow in on_partial_upload_workflows:
                    workflow_id = workflow.get("workflow_id")
                    execution_role = workflow.get("execution_role")
                    if workflow_id and execution_role:
                        on_partial_upload.append(
                            {"WorkflowId": workflow_id, "ExecutionRole": execution_role}
                        )
        server = {
            "Certificate": self.certificate,
            "Domain": self.domain,
            "EndpointType": self.endpoint_type,
            "HostKeyFingerprint": self.host_key_fingerprint,
            "IdentityProviderType": self.identity_provider_type,
            "LoggingRole": self.logging_role,
            "PostAuthenticationLoginBanner": self.post_authentication_login_banner,
            "PreAuthenticationLoginBanner": self.pre_authentication_login_banner,
            "Protocols": self.protocols,
            "SecurityPolicyName": self.security_policy_name,
            "StructuredLogDestinations": self.structured_log_destinations,
            "Arn": self.arn,
            "As2ServiceManagedEgressIpAddresses": self.as2_service_managed_egress_ip_addresses,
            "ServerId": self.server_id,
            "State": self.state,
            "Tags": self.tags,
            "UserCount": self.user_count,
            "EndpointDetails": {
                "AddressAllocationIds": self.endpoint_details.get(
                    "address_allocation_ids"
                ),
                "SubnetIds": self.endpoint_details.get("subnet_ids"),
                "VpcEndpointId": self.endpoint_details.get("vpc_endpoint_id"),
                "VpcId": self.endpoint_details.get("vpc_id"),
                "SecurityGroupIds": self.endpoint_details.get("security_group_ids"),
            },
            "IdentityProviderDetails": {
                "Url": self.identity_provider_details.get("url"),
                "InvocationRole": self.identity_provider_details.get("invocation_role"),
                "DirectoryId": self.identity_provider_details.get("directory_id"),
                "Function": self.identity_provider_details.get("function"),
                "SftpAuthenticationMethods": self.identity_provider_details.get(
                    "sftp_authentication_methods"
                ),
            },
            "ProtocolDetails": {
                "PassiveIp": self.protocol_details.get("passive_ip"),
                "TlsSessionResumptionMode": self.protocol_details.get(
                    "tls_session_resumption_mode"
                ),
                "SetStatOption": self.protocol_details.get("set_stat_option"),
                "As2Transports": self.protocol_details.get("as2_transports"),
            },
            "S3StorageOptions": {
                "DirectoryListingOptimization": self.s3_storage_options.get(
                    "directory_listing_optimization"
                )
            },
            "WorkflowDetails": {
                "OnUpload": on_upload,
                "OnPartialUpload": on_partial_upload,
            },
        }
        return prune_unset(server)

    def to_short_dict(self) -> dict[str, Any]:
        return {
            "Arn": self.arn,
            "Domain": self.domain,
            "EndpointType": self.endpoint_type,
            "IdentityProviderType": self.identity_provider_type,
            "LoggingRole": self.logging_role,
            "ServerId": self.server_id,
            "State": self.state,
            "UserCount": self.user_count,
        }


@dataclass
class Connector(BaseModel):  # type: ignore[misc]
    # TODO: EgressConfig (VpcLattice) not implemented
    region_name: str
    account_id: str
    url: str
    access_role: str
    logging_role: str | None
    as2_config: dict[str, Any] | None
    sftp_config: dict[str, Any] | None
    security_policy_name: str | None
    arn: str = field(default="", init=False)
    connector_id: str = field(default="", init=False)
    tags: list[dict[str, str]] = field(default_factory=list)
    # Egress IPs are assigned automatically when creating a connector.
    service_managed_egress_ip_addresses: list[str] = field(
        default_factory=list, init=False
    )

    def __post_init__(self) -> None:
        if self.connector_id == "":
            self.connector_id = f"c-{mock_random.get_random_hex(17)}"
        if self.arn == "":
            self.arn = f"arn:aws:transfer:{self.region_name}:{self.account_id}:connector/{self.connector_id}"
        # Set to mock values. Actual AWS values used were not validated.
        self.service_managed_egress_ip_addresses = ["127.0.0.1", "127.0.0.2"]

    def to_dict(self) -> dict[str, Any]:
        connector: dict[str, Any] = {
            "Arn": self.arn,
            "ConnectorId": self.connector_id,
            "Url": self.url,
            "AccessRole": self.access_role,
            "LoggingRole": self.logging_role,
            "Tags": self.tags,
            "SecurityPolicyName": self.security_policy_name,
        }
        if self.service_managed_egress_ip_addresses:
            connector["ServiceManagedEgressIpAddresses"] = (
                self.service_managed_egress_ip_addresses
            )
        if self.as2_config:
            as2 = {
                "LocalProfileId": self.as2_config.get("local_profile_id"),
                "PartnerProfileId": self.as2_config.get("partner_profile_id"),
                "MessageSubject": self.as2_config.get("message_subject"),
                "Compression": self.as2_config.get("compression"),
                "EncryptionAlgorithm": self.as2_config.get("encryption_algorithm"),
                "SigningAlgorithm": self.as2_config.get("signing_algorithm"),
                "MdnSigningAlgorithm": self.as2_config.get("mdn_signing_algorithm"),
                "MdnResponse": self.as2_config.get("mdn_response"),
                "BasicAuthSecretId": self.as2_config.get("basic_auth_secret_id"),
                "PreserveContentType": self.as2_config.get("preserve_content_type"),
            }
            # TODO: AsyncMdnConfig not implemented
            connector["As2Config"] = as2
        if self.sftp_config:
            connector["SftpConfig"] = {
                "UserSecretId": self.sftp_config.get("user_secret_id"),
                "TrustedHostKeys": self.sftp_config.get("trusted_host_keys"),
                "MaxConcurrentConnections": self.sftp_config.get(
                    "max_concurrent_connections"
                ),
            }
        return prune_unset(connector)

    def to_short_dict(self) -> dict[str, str]:
        return {
            "Arn": self.arn,
            "ConnectorId": self.connector_id,
            "Url": self.url,
        }
