import frappe

class DbService:
    
    @staticmethod
    def exists(doctype: str, name: str) -> bool:
        """Check if a document exists by doctype and name."""
        result = frappe.db.exists(doctype, name) is not None
        print(f"DEBUG: {doctype} existence for {name}: {result}")
        return result
    
    @staticmethod
    def get(doctype: str, name: str):
        """Retrieve a document by doctype and name. Returns None if not found."""
        print(f"DEBUG: Retrieving {doctype} document with name: {name}")
        if DbService.exists(doctype, name):
            doc = frappe.get_doc(doctype, name)
            print(f"DEBUG: {doctype} document found.")
            return doc
        print(f"DEBUG: {doctype} document not found.")
        return None
    
    @staticmethod
    def get_or_throw(doctype: str, name: str):
        """Retrieve a document by doctype and name or throw an error if not found."""
        doc = DbService.get(doctype, name)
        if doc:
            return doc
        raise ValueError(f"{doctype} document with name {name} does not exist.")
    
    @staticmethod
    def get_value(doctype: str, name: str, fieldname: str):
        """Get a specific field value of a document. Returns None if document does not exist."""
        print(f"DEBUG: Getting value of field {fieldname} from {doctype} {name}")
        if not DbService.exists(doctype, name):
            print("DEBUG: Value cannot be retrieved; document does not exist.")
            return None
        value = frappe.db.get_value(doctype, name, fieldname)
        print(f"DEBUG: Retrieved value: {value}")
        return value
    
    @staticmethod
    def get_value_or_throw(doctype: str, name: str, fieldname: str):
        """Get a specific field value of a document or throw an error if document does not exist."""
        value = DbService.get_value(doctype, name, fieldname)
        if value is not None:
            return value
        raise ValueError(f"{doctype} document with name {name} does not exist.")
    
    @staticmethod
    def create(doctype: str, data: dict):
        """Create a new document of the specified doctype."""
        print(f"DEBUG: Creating new {doctype} document with data: {data}")
        doc = frappe.get_doc({
            "doctype": doctype,
            **data
        })
        doc.insert(ignore_permissions=True)
        print(f"DEBUG: Created new {doctype} document with name: {doc.name}")
        return doc
    
    @staticmethod
    def set_value(doctype: str, name: str, fieldname: str, value: any, save: bool = True):
        """Set a specific field value of a document."""
        print(f"DEBUG: Setting value of field {fieldname} in {doctype} {name} to {value}")
        if save:
            print("DEBUG: Saving updated document.")
            doc = DbService.get_or_throw(doctype, name)
            setattr(doc, fieldname, value)
            doc.save(ignore_permissions=True)
            return doc
        else:
            print("DEBUG: Not saving document as save=False.")
            frappe.db.set_value(doctype, name, fieldname, value)
            return None
    
    @staticmethod
    def update(doctype: str, name: str, update_data: dict, save: bool = True):
        """Update an existing document of the specified doctype."""
        print(f"DEBUG: Updating {doctype} {name}")
        doc = DbService.get_or_throw(doctype, name)
        for key, value in update_data.items():
            setattr(doc, key, value)
            if save:
                doc.save(ignore_permissions=True)
            else:
                DbService.set_value(doctype, name, key, value, save=False)
        print(f"DEBUG: Updated {doctype} document: {doc.as_dict()}")
        return doc
    
    