"""Odoo backend — wraps XML-RPC calls to a running Odoo instance.

The Odoo server is a HARD DEPENDENCY. This CLI is useless without it.
If the server is not reachable, operations fail with clear error messages.
"""

import base64
import xmlrpc.client
from pathlib import Path
from typing import Any

# Re-export shared utilities and ABC for backward compatibility
from cli_anything.odoo_12.utils.odoo_backend_base import (  # noqa: F401
    OdooBackend,
    CONFIG_DIR,
    CONFIG_FILE,
    DEFAULT_DB,
    DEFAULT_PASSWORD,
    DEFAULT_URL,
    DEFAULT_USER,
    get_connection_params,
    load_config,
    save_config,
)


class OdooRPC(OdooBackend):
    """XML-RPC client for a running Odoo instance.

    This is the backend that invokes the REAL Odoo server.
    All operations go through XML-RPC — no reimplementation.
    """

    def __init__(self, url: str, db: str, user: str, password: str):
        super().__init__(url, db, user, password)

        self._common = xmlrpc.client.ServerProxy(
            f"{self.url}/xmlrpc/2/common", allow_none=True
        )
        self._object = xmlrpc.client.ServerProxy(
            f"{self.url}/xmlrpc/2/object", allow_none=True
        )
        self._db = xmlrpc.client.ServerProxy(
            f"{self.url}/xmlrpc/2/db", allow_none=True
        )

    def authenticate(self) -> int:
        """Authenticate and return uid. Raises RuntimeError on failure."""
        try:
            uid = self._common.authenticate(
                self.db, self.user, self.password, {}
            )
        except Exception as e:
            raise RuntimeError(
                f"Cannot connect to Odoo at {self.url}\n"
                f"Error: {e}\n\n"
                f"Make sure the Odoo server is running:\n"
                f"  docker-compose up -d\n"
                f"  # Or: odoo-bin -c odoo.conf"
            ) from e

        if not uid:
            raise RuntimeError(
                f"Authentication failed for user '{self.user}' on database '{self.db}'.\n"
                f"Check credentials and database name."
            )
        self.uid = uid
        return uid

    def _ensure_auth(self):
        if self.uid is None:
            self.authenticate()

    def execute_kw(self, model: str, method: str, args: list,
                   kwargs: dict | None = None) -> Any:
        """Execute a method on an Odoo model via XML-RPC."""
        self._ensure_auth()
        try:
            return self._object.execute_kw(
                self.db, self.uid, self.password,
                model, method, args, kwargs or {}
            )
        except xmlrpc.client.Fault as e:
            raise RuntimeError(f"Odoo RPC error on {model}.{method}: {e.faultString}") from e
        except Exception as e:
            raise RuntimeError(f"RPC call failed: {e}") from e

    # ── Record CRUD ─────────────────────────────────────────────

    def search(self, model: str, domain: list, limit: int = 0,
               offset: int = 0, order: str = "") -> list[int]:
        kwargs = {}
        if limit:
            kwargs["limit"] = limit
        if offset:
            kwargs["offset"] = offset
        if order:
            kwargs["order"] = order
        return self.execute_kw(model, "search", [domain], kwargs)

    def search_read(self, model: str, domain: list, fields: list[str] | None = None,
                    limit: int = 0, offset: int = 0, order: str = "") -> list[dict]:
        kwargs = {}
        if fields:
            kwargs["fields"] = fields
        if limit:
            kwargs["limit"] = limit
        if offset:
            kwargs["offset"] = offset
        if order:
            kwargs["order"] = order
        return self.execute_kw(model, "search_read", [domain], kwargs)

    def read(self, model: str, ids: list[int],
             fields: list[str] | None = None) -> list[dict]:
        kwargs = {}
        if fields:
            kwargs["fields"] = fields
        return self.execute_kw(model, "read", [ids], kwargs)

    def create(self, model: str, values: dict) -> int:
        return self.execute_kw(model, "create", [values])

    def write(self, model: str, ids: list[int], values: dict) -> bool:
        return self.execute_kw(model, "write", [ids, values])

    def unlink(self, model: str, ids: list[int]) -> bool:
        return self.execute_kw(model, "unlink", [ids])

    def search_count(self, model: str, domain: list) -> int:
        return self.execute_kw(model, "search_count", [domain])

    def fields_get(self, model: str, attributes: list[str] | None = None) -> dict:
        kwargs = {}
        if attributes:
            kwargs["attributes"] = attributes
        return self.execute_kw(model, "fields_get", [], kwargs)

    # ── Module operations ───────────────────────────────────────

    def module_list(self, state: str | None = None) -> list[dict]:
        domain = []
        if state:
            domain.append(("state", "=", state))
        return self.search_read(
            "ir.module.module", domain,
            fields=["name", "shortdesc", "state", "installed_version", "summary"],
            order="name",
        )

    def module_info(self, name: str) -> dict | None:
        results = self.search_read(
            "ir.module.module",
            [("name", "=", name)],
            fields=["name", "shortdesc", "state", "installed_version",
                    "summary", "description", "author", "website",
                    "category_id", "dependencies_id"],
        )
        return results[0] if results else None

    def module_install(self, name: str) -> dict:
        mods = self.search("ir.module.module", [("name", "=", name)])
        if not mods:
            raise RuntimeError(f"Module '{name}' not found")
        self.execute_kw("ir.module.module", "button_immediate_install", [mods])
        return {"module": name, "action": "install", "status": "done"}

    def module_update(self, name: str) -> dict:
        mods = self.search("ir.module.module", [("name", "=", name)])
        if not mods:
            raise RuntimeError(f"Module '{name}' not found")
        self.execute_kw("ir.module.module", "button_immediate_upgrade", [mods])
        return {"module": name, "action": "update", "status": "done"}

    # ── Database operations ─────────────────────────────────────

    def db_list(self) -> list[str]:
        try:
            return self._db.list()
        except Exception as e:
            raise RuntimeError(f"Cannot list databases: {e}") from e

    def db_create(self, db_name: str, admin_password: str = "admin",
                  demo: bool = False, lang: str = "pt_BR") -> bool:
        try:
            return self._db.create_database(
                admin_password, db_name, demo, lang, admin_password
            )
        except Exception as e:
            raise RuntimeError(f"Cannot create database '{db_name}': {e}") from e

    def db_drop(self, db_name: str, admin_password: str = "admin") -> bool:
        try:
            return self._db.drop(admin_password, db_name)
        except Exception as e:
            raise RuntimeError(f"Cannot drop database '{db_name}': {e}") from e

    def db_backup(self, db_name: str, output_path: str,
                  admin_password: str = "admin",
                  backup_format: str = "zip") -> dict:
        try:
            data = self._db.dump(admin_password, db_name, backup_format)
            raw = data.data if hasattr(data, "data") else base64.b64decode(data)
            Path(output_path).parent.mkdir(parents=True, exist_ok=True)
            with open(output_path, "wb") as f:
                f.write(raw)
            return {
                "database": db_name,
                "output": output_path,
                "size": len(raw),
                "format": backup_format,
            }
        except Exception as e:
            raise RuntimeError(f"Backup failed for '{db_name}': {e}") from e

    def db_restore(self, db_name: str, backup_path: str,
                   admin_password: str = "admin") -> bool:
        try:
            with open(backup_path, "rb") as f:
                data = base64.b64encode(f.read()).decode()
            return self._db.restore(admin_password, db_name, data)
        except Exception as e:
            raise RuntimeError(f"Restore failed for '{db_name}': {e}") from e

    # ── Report generation ───────────────────────────────────────

    def render_report(self, report_name: str, record_ids: list[int],
                      output_path: str) -> dict:
        """Render a report via the REAL Odoo report engine."""
        self._ensure_auth()
        try:
            report_data = self._object.execute_kw(
                self.db, self.uid, self.password,
                "ir.actions.report", "render_report",
                [report_name, record_ids],
            )
            if isinstance(report_data, (list, tuple)):
                pdf_data = report_data[0]
            else:
                pdf_data = report_data

            if isinstance(pdf_data, str):
                raw = base64.b64decode(pdf_data)
            elif hasattr(pdf_data, "data"):
                raw = pdf_data.data
            else:
                raw = pdf_data

            Path(output_path).parent.mkdir(parents=True, exist_ok=True)
            with open(output_path, "wb") as f:
                f.write(raw)

            return {
                "report": report_name,
                "record_ids": record_ids,
                "output": output_path,
                "size": len(raw),
            }
        except Exception as e:
            raise RuntimeError(f"Report render failed: {e}") from e

    # ── Server info ─────────────────────────────────────────────

    def server_version(self) -> str:
        try:
            return self._common.version()
        except Exception as e:
            raise RuntimeError(f"Cannot get server version: {e}") from e

    def check_connection(self) -> dict:
        """Check if Odoo is reachable and authenticate."""
        version = self.server_version()
        uid = self.authenticate()
        return {
            "url": self.url,
            "database": self.db,
            "user": self.user,
            "uid": uid,
            "server_version": version,
        }
