import frappe

class ItemService:
    
    @staticmethod
    def get_item_category(item_code: str) -> str:
        """Retrieve the category of an Item document by item code."""
        print(f"DEBUG: Getting category for Item {item_code}")
        category = frappe.db.get_value("Item", item_code, "item_group")
        print(f"DEBUG: Retrieved category: {category}")
        return category
    
    @staticmethod
    def get_full_dict(item_code: str) -> frappe._dict:
        """Retrieve the full Item document by item code."""
        print(f"DEBUG: Getting full document for Item {item_code}")
        item_doc = frappe.get_doc("Item", item_code).as_dict()
        item_doc["bom"] = ItemService.get_full_bom_dict(item_code) if item_doc.get("default_bom") else None
        return item_doc
    
    @staticmethod
    def get_full_bom_dict(item_code: str):
        """Retrieve the Bill of Materials (BOM) associated with an Item."""
        print(f"DEBUG: Getting BOM for Item {item_code}")
        bom_name = frappe.db.get_value("BOM", {"item": item_code, "is_active": 1}, "name")
        bom_dict = frappe.get_doc("BOM", bom_name).as_dict()
        for item in bom_dict.get('items', []):
            bom_no = item.get("bom_no")
            if bom_no:
                bom_item_code = frappe.db.get_value("BOM", bom_no, "item")
                item["bom"] = ItemService.get_full_bom_dict(bom_item_code)
        return bom_dict
    
    @staticmethod
    def exists(item_code: str) -> bool:
        """Check if an Item document exists by item code."""
        print(f"DEBUG: Checking existence of Item {item_code}")
        exists = frappe.db.exists("Item", item_code) is not None
        print(f"DEBUG: Item {item_code} exists: {exists}")
        return exists
    
    @staticmethod
    def get_child_groups(item_group: str) -> list[str]:
        """Retrieve all child item groups of a given item group."""
        print(f"DEBUG: Getting child groups for Item Group {item_group}")
        children = []
        child_groups = frappe.get_all("Item Group", filters={"parent_item_group": item_group}, pluck="name")
        if child_groups:
            children.extend(child_groups)
            print(f"DEBUG: Found child groups: {child_groups}. Checking for further children.")
            for child_group in child_groups:
                additional_child_groups = ItemService.get_child_groups(child_group)
                children.extend(additional_child_groups)
                
        print(f"DEBUG: Retrieved child groups: {child_groups}")
        return children
    
    @staticmethod
    def get_item_names_by_group(item_groups: set[str]) -> list[str]:
        """Retrieve item names for items belonging to the specified item groups."""
        print(f"DEBUG: Getting item names for Item Groups {item_groups}")
        items = frappe.get_all("Item", filters={"item_group": ["in", list(item_groups)]}, pluck="name")
        print(f"DEBUG: Retrieved item names: {items}")
        return items
    
    @staticmethod
    def get_items_by_groups(item_groups: list[str]) -> list[dict]:
        """Retrieve all items belonging to the specified item groups."""
        print(f"DEBUG: Getting items for Item Groups {item_groups}")
        all_groups = set(item_groups)
        for group in item_groups:
            all_groups.update(ItemService.get_child_groups(group))
        
        # Batch fetch all items at once with all needed fields
        items = frappe.get_all(
            "Item",
            filters={"item_group": ["in", list(all_groups)]},
            fields=[
                "name", "item_code", "item_name", "item_group", "description",
                "standard_rate", "stock_uom", "default_bom"
            ]
        )
        
        # Get all item codes that have BOMs
        items_with_boms = [item for item in items if item.get("default_bom")]
        item_codes_with_boms = [item["item_code"] for item in items_with_boms]
        
        # Batch fetch all BOMs and their nested structure
        bom_dict = ItemService.batch_fetch_boms(item_codes_with_boms) if item_codes_with_boms else {}
        
        # Attach BOMs to items
        for item in items:
            if item.get("default_bom"):
                item["bom"] = bom_dict.get(item["item_code"])
            else:
                item["bom"] = None
        
        print(f"DEBUG: Retrieved {len(items)} items")
        return items
    
    @staticmethod
    def batch_fetch_boms(item_codes: list[str]) -> dict:
        """Batch fetch all BOMs and build nested structure efficiently."""
        if not item_codes:
            return {}
        
        print(f"DEBUG: Batch fetching BOMs for {len(item_codes)} items")
        
        # Fetch all active BOMs for the given items
        boms = frappe.get_all(
            "BOM",
            filters={"item": ["in", item_codes], "is_active": 1},
            fields=["name", "item"]
        )
        
        if not boms:
            return {}
        
        bom_names = [bom["name"] for bom in boms]
        
        # Fetch all BOM items (children) in one query
        bom_items = frappe.get_all(
            "BOM Item",
            filters={"parent": ["in", bom_names]},
            fields=["parent", "item_code", "item_name", "qty", "uom", "bom_no"],
            order_by="idx"
        )
        
        # Group BOM items by their parent BOM
        bom_items_map = {}
        nested_bom_items = set()
        
        for bom_item in bom_items:
            parent = bom_item["parent"]
            if parent not in bom_items_map:
                bom_items_map[parent] = []
            bom_items_map[parent].append(bom_item)
            
            # Track which items have nested BOMs
            if bom_item.get("bom_no"):
                nested_bom_items.add(bom_item["item_code"])
        
        # Recursively fetch nested BOMs if any
        nested_bom_dict = {}
        if nested_bom_items:
            nested_bom_dict = ItemService.batch_fetch_boms(list(nested_bom_items))
        
        # Build the result dictionary mapping item_code to its BOM structure
        result = {}
        for bom in boms:
            bom_name = bom["name"]
            item_code = bom["item"]
            
            items = bom_items_map.get(bom_name, [])
            # Attach nested BOMs to items
            for item in items:
                if item.get("bom_no"):
                    item["bom"] = nested_bom_dict.get(item["item_code"])
                else:
                    item["bom"] = None
            
            result[item_code] = {
                "name": bom_name,
                "items": items
            }
        
        return result
    
    @staticmethod
    def build_category_dict(items: list[dict]) -> dict:
        """Build a dictionary categorizing items by their item group."""
        print(f"DEBUG: Building category dictionary for items")
        category_dict = {}
        category_dict["Packages"] = {}
        for item in items:
            if item.get("bom"):
                if item.get("item_group", "Uncategorized") not in category_dict["Packages"]:
                    category_dict["Packages"][item.get("item_group", "Uncategorized")] = []
                category_dict["Packages"][item.get("item_group", "Uncategorized")].append(item)
            else:
                category = item.get("item_group", "Uncategorized")
                if category not in category_dict:
                    category_dict[category] = []
                category_dict[category].append(item)
        print(f"DEBUG: Built category dictionary with categories: {list(category_dict.keys())}")
        return category_dict
    
    @staticmethod
    def build_item_code(prefix: str, item_name: str) -> str:
        """Build a unique item code based on the provided prefix and item name."""
        print(f"DEBUG: Building item code with prefix: {prefix} and item name: {item_name}")
        # Replace all " " with "-" and convert to uppercase
        base_code = f"{prefix}-{item_name.replace(' ', '-').upper()}"
        # Check for existing items with the same base code and append a number if necessary
        existing_codes = frappe.get_all("Item", filters={"item_code": ["like", f"{base_code}-%"]}, pluck="item_code")
        if base_code in existing_codes:
            suffix = 1
            while f"{base_code}-{suffix}" in existing_codes:
                suffix += 1
            final_code = f"{base_code}-{suffix}"
        else:
            final_code = base_code
        print(f"DEBUG: Built item code: {final_code}")
        return final_code