import datetime
from collections import defaultdict
from typing import Any, Optional

from dateutil import parser

from moto.core.base_backend import BackendDict, BaseBackend
from moto.core.common_models import BaseModel
from moto.core.utils import iso_8601_datetime_with_milliseconds, unix_time
from moto.moto_api._internal import mock_random
from moto.utilities.utils import get_partition


class CodeBuildProjectMetadata(BaseModel):
    def __init__(
        self,
        account_id: str,
        region_name: str,
        project_name: str,
        source_version: Optional[str],
        artifacts: Optional[dict[str, Any]],
        build_id: str,
        service_role: str,
    ):
        current_date = iso_8601_datetime_with_milliseconds()
        self.build_metadata: dict[str, Any] = {}

        self.build_metadata["id"] = build_id
        self.build_metadata["arn"] = (
            f"arn:{get_partition(region_name)}:codebuild:{region_name}:{account_id}:build/{build_id}"
        )

        self.build_metadata["buildNumber"] = mock_random.randint(1, 100)
        self.build_metadata["startTime"] = current_date
        self.build_metadata["currentPhase"] = "QUEUED"
        self.build_metadata["buildStatus"] = "IN_PROGRESS"
        self.build_metadata["sourceVersion"] = (
            source_version if source_version else "refs/heads/main"
        )
        self.build_metadata["projectName"] = project_name

        self.build_metadata["phases"] = [
            {
                "phaseType": "SUBMITTED",
                "phaseStatus": "SUCCEEDED",
                "startTime": current_date,
                "endTime": current_date,
                "durationInSeconds": 0,
            },
            {"phaseType": "QUEUED", "startTime": current_date},
        ]

        self.build_metadata["source"] = {
            "type": "CODECOMMIT",  # should be different based on what you pass in
            "location": "https://git-codecommit.eu-west-2.amazonaws.com/v1/repos/testing",
            "gitCloneDepth": 1,
            "gitSubmodulesConfig": {"fetchSubmodules": False},
            "buildspec": "buildspec/stuff.yaml",  # should present in the codebuild project somewhere
            "insecureSsl": False,
        }

        self.build_metadata["secondarySources"] = []
        self.build_metadata["secondarySourceVersions"] = []
        self.build_metadata["artifacts"] = artifacts
        self.build_metadata["secondaryArtifacts"] = []
        self.build_metadata["cache"] = {"type": "NO_CACHE"}

        self.build_metadata["environment"] = {
            "type": "LINUX_CONTAINER",
            "image": "aws/codebuild/amazonlinux2-x86_64-standard:3.0",
            "computeType": "BUILD_GENERAL1_SMALL",
            "environmentVariables": [],
            "privilegedMode": False,
            "imagePullCredentialsType": "CODEBUILD",
        }

        self.build_metadata["serviceRole"] = service_role

        self.build_metadata["logs"] = {
            "deepLink": "https://console.aws.amazon.com/cloudwatch/home?region=eu-west-2#logEvent:group=null;stream=null",
            "cloudWatchLogsArn": f"arn:{get_partition(region_name)}:logs:{region_name}:{account_id}:log-group:null:log-stream:null",
            "cloudWatchLogs": {"status": "ENABLED"},
            "s3Logs": {"status": "DISABLED", "encryptionDisabled": False},
        }

        self.build_metadata["timeoutInMinutes"] = 45
        self.build_metadata["queuedTimeoutInMinutes"] = 480
        self.build_metadata["buildComplete"] = False
        self.build_metadata["initiator"] = "rootme"
        self.build_metadata["encryptionKey"] = (
            f"arn:{get_partition(region_name)}:kms:{region_name}:{account_id}:alias/aws/s3"
        )


class CodeBuild(BaseModel):
    def __init__(
        self,
        account_id: str,
        region: str,
        project_name: str,
        description: Optional[str],
        project_source: dict[str, Any],
        artifacts: dict[str, Any],
        environment: dict[str, Any],
        serviceRole: str = "some_role",
        tags: Optional[list[dict[str, str]]] = None,
        cache: Optional[dict[str, Any]] = None,
        timeout: Optional[int] = 0,
        queued_timeout: Optional[int] = 0,
        source_version: Optional[str] = None,
        logs_config: Optional[dict[str, Any]] = None,
        vpc_config: Optional[dict[str, Any]] = None,
    ):
        self.arn = f"arn:{get_partition(region)}:codebuild:{region}:{account_id}:project/{project_name}"
        self.service_role = serviceRole
        self.tags = tags
        current_date = unix_time()
        self.project_metadata: dict[str, Any] = {}

        self.project_metadata["name"] = project_name
        if description:
            self.project_metadata["description"] = description
        self.project_metadata["arn"] = self.arn
        self.project_metadata["encryptionKey"] = (
            f"arn:{get_partition(region)}:kms:{region}:{account_id}:alias/aws/s3"
        )
        if serviceRole.startswith("arn:"):
            self.project_metadata["serviceRole"] = serviceRole
        else:
            self.project_metadata["serviceRole"] = (
                f"arn:{get_partition(region)}:iam::{account_id}:role/service-role/{serviceRole}"
            )
        self.project_metadata["lastModifiedDate"] = current_date
        self.project_metadata["created"] = current_date
        self.project_metadata["badge"] = {}
        self.project_metadata["badge"]["badgeEnabled"] = (
            False  # this false needs to be a json false not a python false
        )
        self.project_metadata["environment"] = environment
        self.project_metadata["artifacts"] = artifacts
        self.project_metadata["source"] = project_source
        self.project_metadata["cache"] = cache or {"type": "NO_CACHE"}
        self.project_metadata["timeoutInMinutes"] = timeout or 0
        self.project_metadata["queuedTimeoutInMinutes"] = queued_timeout or 0
        self.project_metadata["tags"] = tags
        if source_version:
            self.project_metadata["sourceVersion"] = source_version
        if logs_config:
            self.project_metadata["logsConfig"] = logs_config
        if vpc_config:
            self.project_metadata["vpcConfig"] = vpc_config


class CodeBuildBackend(BaseBackend):
    def __init__(self, region_name: str, account_id: str):
        super().__init__(region_name, account_id)
        self.codebuild_projects: dict[str, CodeBuild] = {}
        self.build_history: dict[str, list[str]] = {}
        self.build_metadata: dict[str, CodeBuildProjectMetadata] = {}
        self.build_metadata_history: dict[str, list[dict[str, Any]]] = defaultdict(list)

    def create_project(
        self,
        project_name: str,
        description: Optional[str],
        project_source: dict[str, Any],
        artifacts: dict[str, Any],
        environment: dict[str, Any],
        service_role: str,
        tags: Optional[list[dict[str, str]]],
        cache: Optional[dict[str, Any]],
        timeout: Optional[int],
        queued_timeout: Optional[int],
        source_version: Optional[str],
        logs_config: Optional[dict[str, Any]],
        vpc_config: Optional[dict[str, Any]],
    ) -> dict[str, Any]:
        self.codebuild_projects[project_name] = CodeBuild(
            self.account_id,
            self.region_name,
            project_name=project_name,
            description=description,
            project_source=project_source,
            artifacts=artifacts,
            environment=environment,
            serviceRole=service_role,
            tags=tags,
            cache=cache,
            timeout=timeout,
            queued_timeout=queued_timeout,
            source_version=source_version,
            logs_config=logs_config,
            vpc_config=vpc_config,
        )

        # empty build history
        self.build_history[project_name] = []

        return self.codebuild_projects[project_name].project_metadata

    def list_projects(self) -> list[str]:
        projects = []

        for project in self.codebuild_projects.keys():
            projects.append(project)

        return projects

    def batch_get_projects(self, names: list[str]) -> list[dict[str, Any]]:
        result = []
        for name in names:
            if name in self.codebuild_projects:
                result.append(self.codebuild_projects[name].project_metadata)
            elif name.startswith("arn:"):
                for project in self.codebuild_projects.values():
                    if name == project.arn:
                        result.append(project.project_metadata)
        return result

    def start_build(
        self,
        project_name: str,
        source_version: Optional[str] = None,
        artifact_override: Optional[dict[str, Any]] = None,
    ) -> dict[str, Any]:
        project = self.codebuild_projects[project_name]
        build_id = f"{project_name}:{mock_random.uuid4()}"

        # construct a new build
        self.build_metadata[project_name] = CodeBuildProjectMetadata(
            self.account_id,
            self.region_name,
            project_name,
            source_version,
            artifact_override,
            build_id,
            project.service_role,
        )

        self.build_history[project_name].append(build_id)

        # update build histroy with metadata for build id
        self.build_metadata_history[project_name].append(
            self.build_metadata[project_name].build_metadata
        )

        return self.build_metadata[project_name].build_metadata

    def _set_phases(self, phases: list[dict[str, Any]]) -> list[dict[str, Any]]:
        current_date = iso_8601_datetime_with_milliseconds()
        # No phaseStatus for QUEUED on first start
        for existing_phase in phases:
            if existing_phase["phaseType"] == "QUEUED":
                existing_phase["phaseStatus"] = "SUCCEEDED"

        statuses = [
            "PROVISIONING",
            "DOWNLOAD_SOURCE",
            "INSTALL",
            "PRE_BUILD",
            "BUILD",
            "POST_BUILD",
            "UPLOAD_ARTIFACTS",
            "FINALIZING",
            "COMPLETED",
        ]

        for status in statuses:
            phase: dict[str, Any] = {}
            phase["phaseType"] = status
            phase["phaseStatus"] = "SUCCEEDED"
            phase["startTime"] = current_date
            phase["endTime"] = current_date
            phase["durationInSeconds"] = mock_random.randint(10, 100)
            phases.append(phase)

        return phases

    def batch_get_builds(self, ids: list[str]) -> list[dict[str, Any]]:
        batch_build_metadata: list[dict[str, Any]] = []

        for metadata in self.build_metadata_history.values():
            for build in metadata:
                if build["id"] in ids:
                    build["phases"] = self._set_phases(build["phases"])
                    build["endTime"] = iso_8601_datetime_with_milliseconds(
                        parser.parse(build["startTime"])
                        + datetime.timedelta(minutes=mock_random.randint(1, 5))
                    )
                    build["currentPhase"] = "COMPLETED"
                    build["buildStatus"] = "SUCCEEDED"

                    batch_build_metadata.append(build)

        return batch_build_metadata

    def list_builds_for_project(self, project_name: str) -> list[str]:
        try:
            return self.build_history[project_name]
        except KeyError:
            return []

    def list_builds(self) -> list[str]:
        ids = []

        for build_ids in self.build_history.values():
            ids += build_ids
        return ids

    def delete_project(self, project_name: str) -> None:
        self.build_metadata.pop(project_name, None)
        self.codebuild_projects.pop(project_name, None)

    def stop_build(self, build_id: str) -> Optional[dict[str, Any]]:  # type: ignore[return]
        for metadata in self.build_metadata_history.values():
            for build in metadata:
                if build["id"] == build_id:
                    # set completion properties with variable completion time
                    build["phases"] = self._set_phases(build["phases"])
                    build["endTime"] = iso_8601_datetime_with_milliseconds(
                        parser.parse(build["startTime"])
                        + datetime.timedelta(minutes=mock_random.randint(1, 5))
                    )
                    build["currentPhase"] = "COMPLETED"
                    build["buildStatus"] = "STOPPED"

                    return build


codebuild_backends = BackendDict(CodeBuildBackend, "codebuild")
