import json
import os
import traceback

# DB directory: project root / db (writable without root)
def _db_dir():
    try:
        # src/models/model.py -> project root
        base = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
        return os.path.join(base, "db")
    except Exception:
        return "db"

class Model(dict):
    def __new__(cls, *args, **kwargs):
        # Singleton pattern: return existing instance if it exists
        if not hasattr(cls, '_instance'):
            cls._instance = super().__new__(cls)
        return cls._instance
    
    def __init__(self):
        # Only initialize once (check if already initialized)
        if hasattr(self, '_initialized'):
            return
        
        db_dir = _db_dir()
        try:
            os.makedirs(db_dir, exist_ok=True)
        except OSError:
            pass
        self.class_name = self.__class__.__name__
        self.file = os.path.join(db_dir, f"{self.class_name.lower()}.json")
        super().__init__()

        self.load()  # Load settings from file during initialization
        self._initialized = True

    def set_defaults(self):
        self.clear()

    def get_next_id(self):
        """Get the next available ID for creating a new record."""
        if not self:
            return "1"
        max_id = max((int(k) for k in self.keys() if k.isdigit()), default=0)
        return str(max_id + 1)

    def save(self):
        try:
            db_dir = os.path.dirname(self.file)
            try:
                os.makedirs(db_dir, exist_ok=True)
            except OSError:
                pass
            j = json.dumps(self)
            with open(self.file, 'w') as file:
                file.write(j)
                file.flush()  # Ensure data is written to buffer
            # Try to sync filesystem if available (MicroPython)
            try:
                os.sync()
            except (AttributeError, OSError):
                pass  # os.sync() not available on all platforms
            print(f"{self.class_name} saved successfully to {self.file}")
        except Exception as e:
            print(f"Error saving {self.class_name} to {self.file}: {e}")
            traceback.print_exception(type(e), e, e.__traceback__)

    def load(self):
        try:
            # Check if file exists first
            try:
                with open(self.file, 'r') as file:
                    content = file.read().strip()
            except OSError:
                # File doesn't exist
                raise
            
            if not content:
                # Empty file
                loaded_settings = {}
            else:
                # Parse JSON content
                loaded_settings = json.loads(content)
            
            # Verify it's a dictionary
            if not isinstance(loaded_settings, dict):
                raise ValueError(f"File does not contain a dictionary, got {type(loaded_settings)}")
            
            # Clear and update with loaded data
            # Clear first
            self.clear()
            # Manually copy items to avoid any update() method issues
            for key, value in loaded_settings.items():
                self[key] = value
            print(f"{self.class_name} loaded successfully.")
        except OSError as e:
            # File doesn't exist yet - this is normal on first run
            # Create an empty file with defaults
            self.set_defaults()
            self.save()
            print(f"{self.class_name} initialized (new file created).")
        except ValueError:
            # JSON parsing error - file exists but is corrupted
            # Note: MicroPython uses ValueError for JSON errors, not JSONDecodeError
            print(f"Error loading {self.class_name}: Invalid JSON format. Resetting to defaults.")
            self.set_defaults()
            self.save()
        except Exception:
            # Other unexpected errors - avoid trying to format exception to prevent further errors
            print(f"Error loading {self.class_name}. Resetting to defaults.")
            self.set_defaults()
            self.save()
