from dataclasses import dataclass, field, asdict
from typing import Type
import json


@dataclass
class Meta:
    pass


@dataclass
class GroupsMeta(Meta):
    source: str = None
    group_attack_id: str = None
    country: str = None
    observed_countries: list = None
    observed_motivations: list = None
    target_categories: list = None
    tags: list = None
    owner: str = None


@dataclass
class AssociatedGroupsMeta(Meta):
    id: str = None
    owner_id: str = None
    owner: str = None


@dataclass
class SoftwareMeta(Meta):
    source: str = None
    type: list = None
    software_attack_id: str = None
    platforms: list = None
    tags: list = None
    owner: str = None


@dataclass
class AssociatedSoftwareMeta(Meta):
    id: str = None
    owner_id: str = None
    owner: str = None


@dataclass
class TechniqueMeta(Meta):
    source: str = None
    platforms: list = None
    tags: list = None
    owner: str = None


@dataclass
class SubTechniqueMeta(Meta):
    source: str = None
    technique_attack_id: str = None


@dataclass
class TacticMeta(Meta):
    source: str = None
    tactic_attack_id: str = None
    ordinal_position: str = None
    tags: list = None
    owner: str = None


@dataclass
class ReferencesMeta(Meta):
    source: str = None
    refs: list = None
    title: str = None
    author: str = None
    date_accessed: str = None
    date_published: str = None
    owner: str = None


@dataclass
class CampaignsMeta(Meta):
    source: str = None
    campaign_attack_id: str = None
    first_seen: str = None
    last_seen: str = None
    tags: list = None
    owner: str = None


@dataclass
class ClusterValue:
    description: str = ""
    meta: Meta = field(default_factory=Meta)
    related: list = field(default_factory=list)
    uuid: str = ""
    value: str = ""

    def return_value(self):
        value_dict = asdict(self)
        value_dict["meta"] = {
            k: v for k, v in asdict(self.meta).items() if v is not None and v != []
        }
        return value_dict


class Cluster:
    def __init__(
        self,
        authors: str,
        category: str,
        description: str,
        name: str,
        source: str,
        type: str,
        uuid: str,
        version: int,
    ):
        self.authors = authors
        self.category = category
        self.description = description
        self.name = name
        self.source = source
        self.type = type
        self.uuid = uuid
        self.version = version
        self.values = []
        self.CLUSTER_PATH = "../../clusters"

    def add_values(self, data: dict, meta_class: Type[Meta]):
        pass

    def save_to_file(self, path):
        with open(path, "w") as file:
            file.write(json.dumps(self.__dict__(), indent=4))

    def __str__(self) -> str:
        return f"Cluster: {self.name} - {self.type} - {self.uuid}"

    def __dict__(self) -> dict:
        return {
            "authors": self.authors,
            "category": self.category,
            "description": self.description,
            "name": self.name,
            "source": self.source,
            "type": self.type,
            "uuid": self.uuid,
            "values": self.values,
            "version": self.version,
        }

    def _get_relation_from_mitre_id(
        self, mitre_id: str, cluster: str, meta_key: str, array: bool = False
    ):
        with open(f"{self.CLUSTER_PATH}/{cluster}.json", "r") as file:
            mitre = json.load(file)
        for entry in mitre["values"]:
            try:
                if array:
                    for id in entry["meta"][meta_key]:
                        if id == mitre_id:
                            return entry["uuid"]
                else:
                    if entry["meta"][meta_key] == mitre_id:
                        return entry["uuid"]
            except KeyError:
                continue
        return None


class GroupCluster(Cluster):
    def __init__(
        self,
        authors: str,
        category: str,
        description: str,
        name: str,
        source: str,
        type: str,
        uuid: str,
        version: int,
        enrichment: bool = False,
        subs: bool = False,
    ):
        super().__init__(authors, category, description, name, source, type, uuid, version)
        self.enrichment = enrichment
        self.subs = subs

    def add_values(self, data):
        for entry in data["data"]:
            meta = GroupsMeta(
                source=entry.get("source"),
                group_attack_id=entry.get("group_attack_id"),
                country=(
                    entry.get("country")[0].get("country_code")
                    if entry.get("country")
                    else None
                ),
                observed_countries=[
                    x.get("country_code") for x in entry.get("observed_country")
                ],
                observed_motivations=[
                    x.get("name") for x in entry.get("observed_motivation")
                ],
                target_categories=[x.get("name") for x in entry.get("observed_sector")],
                tags=[x.get("tag") for x in entry.get("tags")],
                owner=entry.get("owner_name"),
            )
            related = []
            if self.enrichment:
                related_cluster = self._get_relation_from_mitre_id(
                    entry.get("group_attack_id"), "threat-actor", "synonyms", True
                )
                if related_cluster:
                    related.append(
                        {
                            "dest-uuid": related_cluster,
                            "type": "similar",
                        }
                    )
            if self.subs:
                for associated_group in entry.get("associated_groups"):
                    found = False
                    for x in self.values:
                        if associated_group.get("associated_group_id") == x.get("uuid"):
                            x["related"].append(
                                {
                                    "dest-uuid": entry.get("id"),
                                    "type": "similar",
                                }
                            )
                            found = True
                            break
                    if found:
                        continue
                    associated_meta = AssociatedGroupsMeta(
                        id=associated_group.get("id"),
                        owner_id=associated_group.get("owner_id"),
                        owner=associated_group.get("owner_name"),
                    )
                    associated_related = []
                    associated_related.append(
                        {
                            "dest-uuid": entry.get("id"),
                            "type": "similar",
                        }
                    )
                    value = ClusterValue(
                        description=associated_group.get("description"),
                        meta=associated_meta,
                        related=associated_related,
                        uuid=associated_group.get("associated_group_id"),
                        value=associated_group.get("name") + " - Associated Group",
                    )
                    self.values.append(value.return_value())
                    related.append(
                        {
                            "dest-uuid": associated_group.get("associated_group_id"),
                            "type": "similar",
                        }
                    )
            value = ClusterValue(
                description=entry.get("description"),
                meta=meta,
                related=related,
                uuid=entry.get("id"),
                value=entry.get("name"),
            )

            # Code Block for handling duplicate from Tidal API data (hopefully only temporary)
            if value.uuid == "3290dcb9-5781-4b87-8fa0-6ae820e152cd":
                value.value = "Volt Typhoon - Tidal" 

            self.values.append(value.return_value())


class SoftwareCluster(Cluster):
    def __init__(
        self,
        authors: str,
        category: str,
        description: str,
        name: str,
        source: str,
        type: str,
        uuid: str,
        version: int,
        enrichment: bool = False,
        subs: bool = False,
    ):
        super().__init__(authors, category, description, name, source, type, uuid, version)
        self.enrichment = enrichment
        self.subs = subs

    def add_values(self, data):
        for entry in data["data"]:
            meta = SoftwareMeta(
                source=entry.get("source"),
                type=[entry.get("type")],
                software_attack_id=entry.get("software_attack_id"),
                platforms=[x.get("name") for x in entry.get("platforms")],
                tags=[x.get("tag") for x in entry.get("tags")],
                owner=entry.get("owner_name"),
            )
            related = []
            for relation in entry.get("groups"):
                related.append(
                    {
                        "dest-uuid": relation.get("group_id"),
                        "type": "used-by",
                    }
                )
            if self.enrichment:
                related_cluster = self._get_relation_from_mitre_id(
                    entry.get("software_attack_id"), "mitre-tool", "external_id"
                )
                if related_cluster:
                    related.append(
                        {
                            "dest-uuid": related_cluster,
                            "type": "similar",
                        }
                    )

                related_cluster = self._get_relation_from_mitre_id(
                    entry.get("software_attack_id"), "mitre-malware", "external_id"
                )
                if related_cluster:
                    related.append(
                        {
                            "dest-uuid": related_cluster,
                            "type": "similar",
                        }
                    )
            if self.subs:
                for associated_software in entry.get("associated_software"):
                    found = False
                    for x in self.values:
                        if associated_software.get("associated_software_id") == x.get("uuid"):
                            x["related"].append(
                                {
                                    "dest-uuid": entry.get("id"),
                                    "type": "similar",
                                }
                            )
                            found = True
                            break
                    if found:
                        continue
                    associated_meta = AssociatedSoftwareMeta(
                        id=associated_software.get("id"),
                        owner_id=associated_software.get("owner_id"),
                        owner=associated_software.get("owner_name"),
                    )
                    associated_related = []
                    associated_related.append(
                        {
                            "dest-uuid": entry.get("id"),
                            "type": "similar",
                        }
                    )
                    value = ClusterValue(
                        description=associated_software.get("description"),
                        meta=associated_meta,
                        related=associated_related,
                        uuid=associated_software.get("associated_software_id"),
                        value=associated_software.get("name") + " - Associated Software",
                    )
                    self.values.append(value.return_value())
                    related.append(
                        {
                            "dest-uuid": associated_software.get(
                                "associated_software_id"
                            ),
                            "type": "similar",
                        }
                    )

            value = ClusterValue(
                description=entry.get("description"),
                meta=meta,
                related=related,
                uuid=entry.get("id"),
                value=entry.get("name"),
            )
            self.values.append(value.return_value())


class TechniqueCluster(Cluster):
    def __init__(
        self,
        authors: str,
        category: str,
        description: str,
        name: str,
        source: str,
        type: str,
        uuid: str,
        version: int,
        subs: bool = False,
    ):
        super().__init__(authors, category, description, name, source, type, uuid, version)
        self.subs = subs

    def add_values(self, data):
        for entry in data["data"]:
            meta = TechniqueMeta(
                source=entry.get("source"),
                platforms=[x.get("name") for x in entry.get("platforms")],
                tags=[x.get("tag") for x in entry.get("tags")],
                owner=entry.get("owner_name"),
            )
            related = []
            for relation in entry.get("tactic"):
                related.append(
                    {
                        "dest-uuid": relation.get("tactic_id"),
                        "type": "uses",
                    }
                )

            if self.subs:
                for sub_technique in entry.get("sub_technique"):
                    sub_meta = SubTechniqueMeta(
                        source=sub_technique.get("source"),
                        technique_attack_id=sub_technique.get("technique_attack_id"),
                    )
                    sub_related = []
                    for relation in sub_technique.get("tactic"):
                        sub_related.append(
                            {
                                "dest-uuid": relation.get("tactic_id"),
                                "type": "uses",
                            }
                        )
                    sub_value = ClusterValue(
                        description=sub_technique.get("description"),
                        meta=sub_meta,
                        related=sub_related,
                        uuid=sub_technique.get("id"),
                        value=sub_technique.get("name"),
                    )

                    # Code for handling duplicate from Tidal API data (hopefully only temporary)
                    if sub_value.uuid == "be637d66-5110-4872-bc15-63b062c3f290":
                        sub_value.value = "Botnet - Duplicate"
                    elif sub_value.uuid == "5c6c3492-5dbc-43ee-a3f2-ba1976d3b379":
                        sub_value.value = "DNS - Duplicate"
                    elif sub_value.uuid == "83e4f633-67fb-4d87-b1b3-8a7a2e60778b":
                        sub_value.value = "DNS Server - Duplicate"
                    elif sub_value.uuid == "b9f5f6b7-ecff-48c8-a23e-c58fd9e41a0d":
                        sub_value.value = "Domains - Duplicate"
                    elif sub_value.uuid == "6e4a0960-dcdc-4e42-9aa1-70d6fc3677b2":
                        sub_value.value = "Server - Duplicate"
                    elif sub_value.uuid == "c30faf84-496b-4f27-a4bc-aa36d583c69f":
                        sub_value.value = "Serverless - Duplicate"
                    elif sub_value.uuid == "2c04d7c8-67a3-4b1a-bd71-47b7c5a54b23":
                        sub_value.value = "Virtual Private Server - Duplicate"
                    elif sub_value.uuid == "2e883e0d-1108-431a-a2dd-98ba98b69417":
                        sub_value.value = "Web Services - Duplicate"
                    elif sub_value.uuid == "d76c3dde-dba5-4748-8d51-c93fc34f885e":
                        sub_value.value = "Cloud Account - Duplicate"
                    elif sub_value.uuid == "12908bde-a5eb-40a5-ae27-d93960d0bfdc":
                        sub_value.value = "Domain Account - Duplicate"
                    elif sub_value.uuid == "df5f6835-ca0a-4ef5-bb3a-b011e4025545":
                        sub_value.value = "Local Account - Duplicate"
                    elif sub_value.uuid == "3c4a2f3a-5877-4a27-a417-76318523657e":
                        sub_value.value = "Cloud Accounts - Duplicate"
                    elif sub_value.uuid == "4b187604-88ab-4972-9836-90a04c705e10":
                        sub_value.value = "Cloud Accounts - Duplicate2"
                    elif sub_value.uuid == "49ae7bf1-a313-41d6-ad4c-74efc4c80ab6":
                        sub_value.value = "Email Accounts - Duplicate"
                    elif sub_value.uuid == "3426077d-3b9c-4f77-a1c6-d68f0dea670e":
                        sub_value.value = "Social Media Accounts - Duplicate"
                    elif sub_value.uuid == "fe595943-f264-4d05-a8c7-7afc8985bfc3":
                        sub_value.value = "Code Repositories - Duplicate"
                    elif sub_value.uuid == "2735f8d1-0e46-4cd7-bfbb-78941bb266fd":
                        sub_value.value = "Steganography - Duplicate"
                    elif sub_value.uuid == "6f152555-36a5-4ec9-8b9b-f0b32c3ccef8":
                        sub_value.value = "Code Signing Certificates - Duplicate"
                    elif sub_value.uuid == "5bcbb0c5-7061-481f-a677-09028a6c59f7":
                        sub_value.value = "Digital Certificates - Duplicate"
                    elif sub_value.uuid == "4c0db4e5-14e0-4fb7-88b0-bb391ce5ad58":
                        sub_value.value = "Digital Certificates - Duplicate2"
                    elif sub_value.uuid == "5a57d258-0b23-431b-b50e-3150d2c0e52c":
                        sub_value.value = "Exploits - Duplicate"
                    elif sub_value.uuid == "0f77a14a-d450-4885-b81f-23eeffa53a7e":
                        sub_value.value = "Malware - Duplicate"
                    elif sub_value.uuid == "ba553ad4-5699-4458-ae4e-76e1faa43291":
                        sub_value.value = "Spearphishing Attachment - Duplicate"
                    elif sub_value.uuid == "d08a9977-9fc2-46bb-84f9-dbb5187c426d":
                        sub_value.value = "Spearphishing Link - Duplicate"
                    elif sub_value.uuid == "350c12a3-33f6-5942-8892-4d6e70abbfc1":
                        sub_value.value = "Spearphishing Voice - Duplicate"
                    
                    self.values.append(sub_value.return_value())
                    related.append(
                        {
                            "dest-uuid": sub_technique.get("id"),
                            "type": "similar",
                        }
                    )

            value = ClusterValue(
                description=entry.get("description"),
                meta=meta,
                related=related,
                uuid=entry.get("id"),
                value=entry.get("name"),
            )
            self.values.append(value.return_value())


class TacticCluster(Cluster):
    def __init__(
        self,
        authors: str,
        category: str,
        description: str,
        name: str,
        source: str,
        type: str,
        uuid: str,
        version: int,
    ):
        super().__init__(authors, category, description, name, source, type, uuid, version)

    def add_values(self, data):
        for entry in data["data"]:
            meta = TacticMeta(
                source=entry.get("source"),
                tactic_attack_id=entry.get("tactic_attack_id"),
                ordinal_position=str(entry.get("ordinal_position")),
                tags=[x.get("tag") for x in entry.get("tags")],
                owner=entry.get("owner_name"),
            )
            related = []
            for relation in entry.get("techniques"):
                related.append(
                    {
                        "dest-uuid": relation.get("technique_id"),
                        "type": "uses",
                    }
                )
            value = ClusterValue(
                description=entry.get("description"),
                meta=meta,
                related=related,
                uuid=entry.get("id"),
                value=entry.get("name"),
            )
            self.values.append(value.return_value())


class ReferencesCluster(Cluster):
    def __init__(
        self,
        authors: str,
        category: str,
        description: str,
        name: str,
        source: str,
        type: str,
        uuid: str,
        version: int,
    ):
        super().__init__(authors, category, description, name, source, type, uuid, version)

    def add_values(self, data):
        for entry in data["data"]:
            meta = ReferencesMeta(
                source=entry.get("source"),
                refs=[entry.get("url")] if entry.get("url") != "" else None,
                title=entry.get("title"),
                author=entry.get("author"),
                date_accessed=entry.get("date_accessed"),
                date_published=entry.get("date_published"),
                owner=entry.get("owner_name"),
            )
            value = ClusterValue(
                description=entry.get("description"),
                meta=meta,
                related=[],
                uuid=entry.get("id"),
                value=entry.get("name"),
            )
            self.values.append(value.return_value())


class CampaignsCluster(Cluster):
    def __init__(
        self,
        authors: str,
        category: str,
        description: str,
        name: str,
        source: str,
        type: str,
        uuid: str,
        version: int,
    ):
        super().__init__(authors, category, description, name, source, type, uuid, version)

    def add_values(self, data):
        for entry in data["data"]:
            meta = CampaignsMeta(
                source=entry.get("source"),
                campaign_attack_id=entry.get("campaign_attack_id"),
                first_seen=entry.get("first_seen"),
                last_seen=entry.get("last_seen"),
                tags=[x.get("tag") for x in entry.get("tags")],
                owner=entry.get("owner_name"),
            )
            related = []
            value = ClusterValue(
                description=entry.get("description"),
                meta=meta,
                related=related,
                uuid=entry.get("id"),
                value=entry.get("name"),
            )
            self.values.append(value.return_value())