import xmlrpc.client
from .exceptions import OdooAuthError, OdooRPCError


class OdooClient:
    """
    A lightweight Python wrapper for the Odoo XML-RPC API.

    Usage:
        from odoo_connect import OdooClient

        odoo = OdooClient("https://mycompany.odoo.com", "my-database")
        odoo.login("admin@example.com", "password")

        products = odoo.search_read("product.template", fields=["name", "list_price"])
    """

    def __init__(self, url: str, db: str):
        """
        Initialize the Odoo client.

        Args:
            url: The base URL of your Odoo instance (e.g. "https://mycompany.odoo.com")
            db:  The database name
        """
        self.url = url.rstrip("/")
        self.db = db
        self.uid = None
        self._password = None

        self._common = xmlrpc.client.ServerProxy(f"{self.url}/xmlrpc/2/common")
        self._models = xmlrpc.client.ServerProxy(f"{self.url}/xmlrpc/2/object")

    # ------------------------------------------------------------------
    # Auth
    # ------------------------------------------------------------------

    def login(self, username: str, password: str) -> int:
        """
        Authenticate against the Odoo instance.

        Args:
            username: Your Odoo login (usually an email)
            password: Your Odoo password or API key

        Returns:
            The user ID (uid)

        Raises:
            OdooAuthError: If authentication fails
        """
        try:
            uid = self._common.authenticate(self.db, username, password, {})
        except Exception as e:
            raise OdooRPCError(f"Connection error: {e}") from e

        if not uid:
            raise OdooAuthError(
                f"Login failed for user '{username}' on database '{self.db}'. "
                "Check your credentials."
            )

        self.uid = uid
        self._password = password
        return uid

    # ------------------------------------------------------------------
    # Internal helper
    # ------------------------------------------------------------------

    def _execute(self, model: str, method: str, *args, **kwargs):
        """Low-level execute_kw wrapper."""
        if not self.uid:
            raise OdooAuthError("Not logged in. Call login() first.")
        try:
            return self._models.execute_kw(
                self.db, self.uid, self._password,
                model, method, args, kwargs
            )
        except xmlrpc.client.Fault as e:
            raise OdooRPCError(f"Odoo error on {model}.{method}: {e.faultString}") from e

    # ------------------------------------------------------------------
    # CRUD operations
    # ------------------------------------------------------------------

    def search(self, model: str, filters: list = None, limit: int = None, offset: int = 0) -> list:
        """
        Search for record IDs matching the given filters.

        Args:
            model:   Odoo model name (e.g. "product.template")
            filters: Domain filter list (e.g. [["active", "=", True]])
            limit:   Max number of results (None = no limit)
            offset:  Number of records to skip

        Returns:
            List of matching record IDs
        """
        domain = filters or []
        kwargs = {"offset": offset}
        if limit is not None:
            kwargs["limit"] = limit
        return self._execute(model, "search", domain, **kwargs)

    def search_read(self, model: str, filters: list = None, fields: list = None,
                    limit: int = None, offset: int = 0) -> list:
        """
        Search and return records with their field values.

        Args:
            model:   Odoo model name
            filters: Domain filter list
            fields:  List of field names to return (None = all fields)
            limit:   Max number of results
            offset:  Number of records to skip

        Returns:
            List of dicts with the requested field values
        """
        domain = filters or []
        kwargs = {"offset": offset}
        if fields:
            kwargs["fields"] = fields
        if limit is not None:
            kwargs["limit"] = limit
        return self._execute(model, "search_read", domain, **kwargs)

    def read(self, model: str, ids: list, fields: list = None) -> list:
        """
        Read specific records by their IDs.

        Args:
            model:  Odoo model name
            ids:    List of record IDs to read
            fields: List of field names to return (None = all fields)

        Returns:
            List of dicts with the requested field values
        """
        kwargs = {}
        if fields:
            kwargs["fields"] = fields
        return self._execute(model, "read", ids, **kwargs)

    def create(self, model: str, values: dict) -> int:
        """
        Create a new record.

        Args:
            model:  Odoo model name
            values: Dict of field values for the new record

        Returns:
            ID of the newly created record
        """
        return self._execute(model, "create", values)

    def write(self, model: str, ids: list, values: dict) -> bool:
        """
        Update existing records.

        Args:
            model:  Odoo model name
            ids:    List of record IDs to update
            values: Dict of field values to update

        Returns:
            True if successful
        """
        return self._execute(model, "write", ids, values)

    def unlink(self, model: str, ids: list) -> bool:
        """
        Delete records.

        Args:
            model: Odoo model name
            ids:   List of record IDs to delete

        Returns:
            True if successful
        """
        return self._execute(model, "unlink", ids)

    def count(self, model: str, filters: list = None) -> int:
        """
        Count records matching the given filters.

        Args:
            model:   Odoo model name
            filters: Domain filter list

        Returns:
            Number of matching records
        """
        domain = filters or []
        return self._execute(model, "search_count", domain)

    # ------------------------------------------------------------------
    # Extras
    # ------------------------------------------------------------------

    def get_fields(self, model: str, attributes: list = None) -> dict:
        """
        Get the field definitions for a model.

        Args:
            model:      Odoo model name
            attributes: List of attributes to return (e.g. ["string", "type"])

        Returns:
            Dict of field definitions
        """
        kwargs = {}
        if attributes:
            kwargs["attributes"] = attributes
        return self._execute(model, "fields_get", [], **kwargs)

    def version(self) -> dict:
        """
        Get the Odoo server version info.

        Returns:
            Dict with version information
        """
        return self._common.version()

    def __repr__(self):
        status = f"uid={self.uid}" if self.uid else "not logged in"
        return f"OdooClient(url='{self.url}', db='{self.db}', {status})"
