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

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

# %% ../../nbs/1pass/op.ipynb 2
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.client import Client
from onepasswordconnectsdk.models import Item, ItemVault, Field
from onepasswordconnectsdk.models.summary_item import SummaryItem

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

from nbdev.showdoc import patch_to

# %% ../../nbs/1pass/op.ipynb 5
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 7
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 8
def clean_dict(obj):
    return {key: value for key, value in obj.items() if value}

# %% ../../nbs/1pass/op.ipynb 9
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 11
@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, obj):
        return cls(id=obj.id, label=obj.label)

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

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

# %% ../../nbs/1pass/op.ipynb 13
@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] = None
    item_section: Item_Section = None

    @classmethod
    def _from_sdk(cls, sdk, client):

        item_section = Item_Section(sdk.section)

        return cls(
            client=client,
            id=sdk.id,
            item_section=item_section,
            label=sdk.label,
            purpose=sdk.purpose,
            value=sdk.value,
            recipe=sdk.recipe,
            is_secret=True if sdk.type == "CONCEALED" else False,
            type=sdk.type,
            is_call_generate_fn=sdk.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(
            value=self.value, purpose=deu.convert_enum_member_to_name(self.purpose)
        )

    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))

    # def to_json(self):
    #     return clean_dict({
    #         "label" :self.label,
    #         "value": self.value,
    #         "purpose": deu.convert_enum_member_to_name(self.purpose),
    #         "type": deu.convert_enum_member_to_name(self.type),
    #         "recipe" : self.recipe,
    #         "generate": self.is_call_generate_fn,
    #         "section": self.item_section.to_json_for_item() if self.item_section else None
    #     })

# %% ../../nbs/1pass/op.ipynb 15
@dataclass
class Vault_Item:
    vault_id: str
    title: str
    category: Union[str, PreferredItemCategory]
    vault_fields: List[Any]
    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
        return self.id == other.id and self.vault_id == other.vault_id

    @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(cfield, client=client) for cfield in sdk_obj.fields
        ]

        return cls(
            client=client,
            id=sdk_obj.id,
            vault_id=sdk_obj.vault.id,
            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(
            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],
        )

    @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 17
@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)
        self.vault_fields.index[target_index] = vault_field

    else:
        self.vault_fields.append(vault_field)

    return self.vault_fields

# %% ../../nbs/1pass/op.ipynb 23
@patch_to(Vault)
async def get_items(self: Vault_Item, return_raw: bool = False):
    sdk_items = await self.client.get_items(self.id)

    if return_raw:
        return await deu.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,
        )

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

    return self.vault_items

# %% ../../nbs/1pass/op.ipynb 29
class VaultItem_GET_Excception(cl.BaseError):
    def __init__(self, message):
        super().__init__(message)


@patch_to(Vault)
async def get_item(self: Vault, item_title=None, item_id=None) -> 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)

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

        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(f"no items returned with {criteria}")

    return vault_item

# %% ../../nbs/1pass/op.ipynb 32
@patch_to(Vault)
async def create_item(
    self: Vault,
    title,
    category,
    tags,
    vault_fields: Item_Field = None,
    debug_api: bool = False,
    return_raw: bool = False,
):

    sdk_fields = None
    if vault_fields:
        sdk_fields = [vault_field.to_sdk() for vault_field in vault_fields]

    sdk_item = Item(
        title=title,
        category=deu.convert_enum_member_to_name(category),
        tags=tags,
        fields=sdk_fields,
    )

    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 36
@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: Item_Field = None,
    debug_prn: bool = False,
):

    vault_item = None

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

        if item_title:
            vault_item.title = item_title

        if tags:
            vault_item.tags = tags

        if vault_fields:
            for vault_field in vault_fields:
                vault_item.add_field(vault_field)

        if debug_prn:
            print("update item")

        await vault_item.update()

    # create vault_item
    except VaultItem_GET_Excception as e:

        if debug_prn:
            print("update item")

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

    return vault_item
