"""
    Date type classes for companion stacks
"""
import posixpath
import re
from typing import Optional

from samcli.lib.utils.hash import str_checksum


class CompanionStack:
    """
    Abstraction class for the companion stack
    Companion stack name will be generated by this class.
    """

    _parent_stack_name: str
    _escaped_parent_stack_name: str
    _parent_stack_hash: str
    _stack_name: str

    def __init__(self, parent_stack_name: str) -> None:
        self._parent_stack_name = parent_stack_name
        self._escaped_parent_stack_name = re.sub(r"[^a-z0-9]", "", self._parent_stack_name.lower())
        self._parent_stack_hash = str_checksum(self._parent_stack_name)
        # There is max 128 characters limit on the length of stack name.
        # Using MD5 to avoid collision after truncating
        # 104 + 1 + 8 + 15 = 128 max char
        self._stack_name = f"{self._parent_stack_name[:104]}-{self._parent_stack_hash[:8]}-CompanionStack"

    @property
    def parent_stack_name(self) -> str:
        """
        Parent stack name
        """
        return self._parent_stack_name

    @property
    def escaped_parent_stack_name(self) -> str:
        """
        Parent stack name with only alphanumeric characters
        """
        return self._escaped_parent_stack_name

    @property
    def parent_stack_hash(self) -> str:
        """
        MD5 hash of parent stack name
        """
        return self._parent_stack_hash

    @property
    def stack_name(self) -> str:
        """
        Companion stack stack name
        """
        return self._stack_name


class ECRRepo:
    """
    Abstraction class for ECR repos in companion stacks
    Logical ID, Physical ID, and Repo URI will be generated with this class.
    """

    _function_full_path: Optional[str]
    _escaped_function_logical_id: Optional[str]
    _function_md5: Optional[str]
    _companion_stack: Optional[CompanionStack]
    _logical_id: Optional[str]
    _physical_id: Optional[str]
    _output_logical_id: Optional[str]

    def __init__(
        self,
        companion_stack: Optional[CompanionStack] = None,
        function_full_path: Optional[str] = None,
        logical_id: Optional[str] = None,
        physical_id: Optional[str] = None,
        output_logical_id: Optional[str] = None,
    ):
        """
        Must be specified either with
        companion_stack and function_full_path
        or
        logical_id, physical_id, and output_logical_id
        """
        self._function_full_path = (
            function_full_path.replace(posixpath.sep, "") if function_full_path else function_full_path
        )
        self._escaped_function_logical_id = (
            re.sub(r"[^a-z0-9]", "", self._function_full_path.lower()) if self._function_full_path is not None else None
        )
        self._function_md5 = str_checksum(self._function_full_path) if self._function_full_path is not None else None
        self._companion_stack = companion_stack

        self._logical_id = logical_id
        self._physical_id = physical_id
        self._output_logical_id = output_logical_id

    @property
    def logical_id(self) -> Optional[str]:
        if self._logical_id is None and self._function_full_path and self._function_md5:
            # MD5 is used to avoid two having the same escaped name with different Lambda Functions
            # For example: Helloworld and HELLO-WORLD
            # 52 + 8 + 4 = 64 max char
            self._logical_id = self._function_full_path[:52] + self._function_md5[:8] + "Repo"
        return self._logical_id

    @property
    def physical_id(self) -> Optional[str]:
        if (
            self._physical_id is None
            and self._companion_stack
            and self._function_md5
            and self._escaped_function_logical_id
        ):
            # The physical ID is constructed with escaped_stack_name + stack_md5[:8] as prefix/path and
            # followed by escaped_lambda_logical_id + function_md5[:8] + "repo" to show
            # the linkage between the function and the repo
            # 128 + 8 + 1 + 64 + 8 + 4 = 213 max char
            self._physical_id = (
                self._companion_stack.escaped_parent_stack_name
                + self._companion_stack.parent_stack_hash[:8]
                + "/"
                + self._escaped_function_logical_id
                + self._function_md5[:8]
                + "repo"
            )
        return self._physical_id

    @property
    def output_logical_id(self) -> Optional[str]:
        if self._output_logical_id is None and self._function_full_path and self._function_md5:
            self._output_logical_id = self._function_full_path[:52] + self._function_md5[:8] + "Out"
        return self._output_logical_id

    @staticmethod
    def get_domain(region: str) -> str:
        # https://docs.amazonaws.cn/en_us/aws/latest/userguide/endpoints-Beijing.html
        if region.startswith("cn-"):
            return "amazonaws.com.cn"
        return "amazonaws.com"

    def get_repo_uri(self, account_id, region) -> str:
        domain = ECRRepo.get_domain(region)
        return f"{account_id}.dkr.ecr.{region}.{domain}/{self.physical_id}"
