# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/1pass/op.ipynb.

# %% auto 0
__all__ = ['PreferredItemCategory', 'KnownItemType', 'KnownItemPurpose', 'generate_password_recipe', 'Vault', 'Item_Section',
           'Item_Field', 'Vault_Item']

# %% ../nbs/1pass/op.ipynb 3
from onepasswordconnectsdk.async_client import AsyncClient as Client

# %% ../nbs/1pass/op.ipynb 4
from dataclasses import dataclass, field
from typing import Any, List, Union
from enum import Enum, auto

import datetime as dt
from pprint import pprint

from onepasswordconnectsdk.models import Item, Field
from onepasswordconnectsdk.models.summary_item import SummaryItem

from domolibrary_extensions.utils import utils as deu
import domolibrary_extensions.utils.execution as dex
import domolibrary_extensions.client as cl

from nbdev.showdoc import patch_to

# %% ../nbs/1pass/op.ipynb 8
class PreferredItemCategory(Enum):
    """preferred types
    complete list of types can be retireved here https://developer.1password.com/docs/connect/connect-api-reference/#add-an-item
    """

    LOGIN = auto()
    PASSWORD = auto()
    API_CREDENTIAL = auto()
    SSH_KEY = auto

# %% ../nbs/1pass/op.ipynb 10
class KnownItemType(Enum):
    """based on list from current vault"""

    STRING = auto()
    CONCEALED = auto()
    DATE = auto()
    MENU = auto()
    URL = auto()


class KnownItemPurpose(Enum):
    NOTES = auto()
    PASSWORD = auto()
    USERNAME = auto()


# set([field.type for item in vault.items for field in item.fields])
# KnownItemPurpose.NOTES.__class__.__name__

# %% ../nbs/1pass/op.ipynb 11
def clean_dict(obj):
    return {key: value for key, value in obj.items() if value}

# %% ../nbs/1pass/op.ipynb 12
def generate_password_recipe(
    length: int = 24, is_letters: bool = True, is_digits: bool = True
):
    characterSets = []
    if is_letters:
        characterSets.append("LETTERS")

    if is_digits:
        characterSets.append("DIGITS")

    return {"length": length, "characterSes": characterSets}

# %% ../nbs/1pass/op.ipynb 14
@dataclass
class Vault:
    client: Client = field(repr=False)  # pass an asynchronous client
    id: str
    name: str

    vault_items = None

    @classmethod
    def from_sdk(cls, obj, client: Client):
        return cls(id=obj.id, name=obj.name, client=client)

    @classmethod
    async def get_vault(
        cls,
        aclient: Client,
        vault_name: str = None,
        vault_id: str = None,
        return_raw: bool = False,
    ):  # pass an asyc enabled client
        sdk_obj = None

        if vault_id:
            sdk_obj = await aclient.get_vault(vault_id)

        if vault_name:
            sdk_obj = await aclient.get_vault_by_title(vault_name)

        if return_raw:
            return sdk_obj

        return cls.from_sdk(sdk_obj, aclient)

# %% ../nbs/1pass/op.ipynb 16
@dataclass
class Item_Section:
    id: str
    label: str = None

    """
    used to group fields in an item.
    must put custom fields into a section
    """

    @classmethod
    def _from_sdk(cls, sdk_obj):
        return cls(id=sdk_obj.id, label=sdk_obj.label)

    def to_sdk(self):
        return clean_dict({"id": self.id, "label": self.label})

    def to_sdk_for_item(self):
        return clean_dict({"id": self.id})

# %% ../nbs/1pass/op.ipynb 18
@dataclass
class Item_Field:
    label: str = None
    value: str = None
    recipe: dict = None
    id: str = None
    is_call_generate_fn: bool = False
    is_secret: bool = False

    client: Client = field(repr=False, default=None)
    type: Union[str, KnownItemType] = None
    purpose: Union[str, KnownItemPurpose] = ""
    item_section: Item_Section = None

    def __eq__(self, other):
        if not isinstance(other, Item_Field):
            return False

        if self.label == other.label:
            return True

        if self.id == other.id and (self.id is not None or other.id is not None):
            return True

        return False

    @classmethod
    def _from_sdk(cls, sdk_obj, client):
        item_section = Item_Section(sdk_obj.section)

        return cls(
            client=client,
            id=sdk_obj.id,
            item_section=item_section,
            label=sdk_obj.label,
            purpose=sdk_obj.purpose,
            value=sdk_obj.value,
            # recipe=sdk_obj.recipe,
            is_secret=True if sdk_obj.type == "CONCEALED" else False,
            type=sdk_obj.type,
            is_call_generate_fn=sdk_obj.generate,
        )

    def generate_recipie(
        self, length: int = 24, is_letters: bool = True, is_digits: bool = True
    ):
        """generates a recipie for how generating passwords is handled"""

        self.recipe = generate_password_recipe(
            length=length, is_letters=is_letters, is_digits=is_digits
        )
        return self.recipe

    def to_sdk(self):
        return Field(
            id=self.id,
            label=self.label,
            value=self.value,
            purpose=deu.convert_enum_member_to_name(self.purpose),
            type=deu.convert_enum_member_to_name(self.type),
            generate=self.is_call_generate_fn,
            # recipe = self.recipe,
            section=self.item_section.to_sdk_for_item() if self.item_section else None,
        )

    def __repr__(self):
        kws = [
            f"{key}={value!r}"
            for key, value in self.__dict__.items()
            if key not in ["client", "is_secret", "is_call_generate_fn"]
            and not (key == "value" and self.is_secret)
        ]
        return "{}({})".format(type(self).__name__, ", ".join(kws))

# %% ../nbs/1pass/op.ipynb 20
@dataclass
class Vault_Item:
    vault: Vault
    title: str
    category: Union[str, PreferredItemCategory]
    vault_fields: List[Item_Field]
    vault_sections: List[Item_Section] = None
    tags: List[str] = None
    created_at: dt.datetime = None
    updated_at: dt.datetime = None
    is_trashed: bool = False
    id: str = None
    client: Client = field(repr=False, default=None)

    def __eq__(self, other):
        if not isinstance(other, Vault_Item):
            return False

        if not self.vault.id == other.vault.id:
            return False

        if self.title == other.title:
            return True

        if self.id == other.id and (self.id is not None or other.id is not None):
            return True

        return False

    @classmethod
    async def _from_sdk(cls, sdk_obj, client):
        if isinstance(sdk_obj, SummaryItem):
            sdk_obj = await client.get_item_by_id(
                vault_id=sdk_obj.vault.id, item_id=sdk_obj.id
            )

        vault_sections = None
        if sdk_obj.sections:
            vault_sections = [
                Item_Section._from_sdk(section) for section in sdk_obj.sections
            ]

        vault_fields = [
            Item_Field._from_sdk(sdk_field, client=client)
            for sdk_field in sdk_obj.fields
        ]

        return cls(
            client=client,
            id=sdk_obj.id,
            vault=await Vault.get_vault(vault_id=sdk_obj.vault.id, aclient=client),
            vault_sections=vault_sections,
            title=sdk_obj.title,
            tags=sdk_obj.tags,
            category=sdk_obj.category,
            created_at=sdk_obj.created_at,
            updated_at=sdk_obj.updated_at,
            is_trashed=sdk_obj.trashed,
            vault_fields=vault_fields,
        )

    def to_sdk(self):
        return Item(
            # "vault": {"id": self.vault_id},
            id=self.id,
            title=self.title,
            category=deu.convert_enum_member_to_name(self.category),
            tags=self.tags,
            fields=(
                [vault_field.to_sdk() for vault_field in self.vault_fields]
                if self.vault_fields
                else None
            ),
            sections=(
                [vault_section.to_sdk() for vault_section in self.vault_sections]
                if self.vault_sections
                else None
            ),
        )

    @classmethod
    async def get_by_id(
        cls, vault_id, item_id, client: Client, return_raw: bool = False
    ):
        sdk_obj = await client.get_item_by_id(vault_id=vault_id, item_id=item_id)

        if return_raw:
            return sdk_obj

        return await cls._from_sdk(sdk_obj=sdk_obj, client=client)

# %% ../nbs/1pass/op.ipynb 22
@patch_to(Vault_Item)
async def delete(self: Vault_Item):
    await self.client.delete_item(self.id, self.vault.id)
    return True


@patch_to(Vault_Item)
async def update(self: Vault_Item, return_raw: bool = False, debug_api: bool = False):
    item_uuid = self.id
    vault_id = self.vault.id
    sdk_item = self.to_sdk()

    if debug_api:
        print({"item_uuid": item_uuid, "vaul_id": vault_id, "item": sdk_item})

    sdk_obj = await self.client.update_item(
        item_uuid=item_uuid, vault_id=vault_id, item=sdk_item
    )

    if return_raw:
        return sdk_obj

    return await Vault_Item._from_sdk(sdk_obj, client=self.client)


@patch_to(Vault_Item)
def add_field(self, vault_field: Item_Field):
    self.vault_fields = self.vault_fields or []

    if vault_field in self.vault_fields:
        target_index = self.vault_fields.index(vault_field)

        ele = self.vault_fields[target_index]
        vault_field.id = ele.id

        self.vault_fields[target_index] = vault_field

    else:
        self.vault_fields.append(vault_field)

    return self.vault_fields

# %% ../nbs/1pass/op.ipynb 26
class VaultItem_GET_Excception(cl.BaseError):
    def __init__(self, vault_str: str, message):  # id or name
        super().__init__(instance=vault_str, message=message)


@patch_to(Vault)
async def get_items(self: Vault, return_raw: bool = False) -> List[Vault_Item]:
    sdk_items = await self.client.get_items(self.id)

    if return_raw:
        return await dex.gather_with_concurrency(
            *[
                self.client.get_item_by_id(vault_id=self.id, item_id=sdk_item.id)
                for sdk_item in sdk_items
            ],
            n=10,
        )

    items = await dex.gather_with_concurrency(
        *[
            Vault_Item._from_sdk(sdk_obj=sdk_item, client=self.client)
            for sdk_item in sdk_items
        ],
        n=10,
    )

    self.items = items
    return items


@patch_to(Vault)
async def get_item(
    self: Vault, item_title=None, item_id=None, return_raw: bool = False
) -> Vault_Item:
    "retrieve an item by title or id"

    vault_item = None

    if item_id:
        vault_item = await self.client.get_item_by_id(
            vault_id=self.id, item_id=item_id, return_raw=return_raw
        )

    if not vault_item and item_title:
        vault_items = await self.get_items(return_raw=return_raw)

        vault_item = next(
            (
                vault_item
                for vault_item in vault_items
                if vault_item.title == item_title
            ),
            None,
        )

    if not vault_item:
        criteria = ""
        if item_id:
            criteria += item_id

        if item_title:
            criteria += f" - {item_title}"

        raise VaultItem_GET_Excception(
            vault_str=(self.name or self.id),
            message=f"no items returned with {criteria}",
        )

    return vault_item

# %% ../nbs/1pass/op.ipynb 28
@patch_to(Vault)
async def create_item(
    self: Vault,
    title: str,
    category: PreferredItemCategory,
    tags: List[str],
    vault_fields: List[Item_Field] = None,
    debug_api: bool = False,
    return_raw: bool = False,
):
    sdk_item = Item(
        title=title,
        category=deu.convert_enum_member_to_name(category),
        tags=tags,
        fields=(
            [vault_field.to_sdk() for vault_field in vault_fields]
            if vault_fields
            else None
        ),
    )

    if debug_api:
        pprint(sdk_item)

    sdk_item = await self.client.create_item(item=sdk_item, vault_id=self.id)

    if return_raw:
        return sdk_item

    return await Vault_Item._from_sdk(sdk_item, self.client)

# %% ../nbs/1pass/op.ipynb 29
@patch_to(Vault)
async def upsert_item(
    self: Vault,
    item_id: str = None,
    item_title: str = None,
    category: PreferredItemCategory = None,
    tags: List[str] = None,
    vault_fields: List[Item_Field] = None,
    debug_prn: bool = False,
    debug_api: bool = False,
):
    vault_fields = vault_fields or []

    vault_item = None

    # try to retrieve vault_item
    try:
        vault_item = await self.get_item(
            item_id=item_id,
            item_title=item_title,
        )

        # update item
        if item_title:
            vault_item.title = item_title

        if tags:
            vault_item.tags = tags

        for vault_field in vault_fields:
            vault_item.add_field(vault_field)

        if debug_prn:
            print("updating item")

        await vault_item.update(debug_api=debug_api)

    # create vault_item
    except VaultItem_GET_Excception as e:
        if debug_prn:
            print("creating item")

        vault_item = await self.create_item(
            title=item_title,
            category=category,
            tags=tags,
            vault_fields=vault_fields,
            debug_api=debug_api,
        )

    return vault_item
