from collections import OrderedDict
from io import BytesIO
from logging import getLogger

from requests import codes as codes_request, post as post_request, get as get_request
from requests.exceptions import RequestException

from odoo import _, api, models, fields
from odoo.exceptions import AccessError, UserError
from odoo.tools.safe_eval import safe_eval, time

_logger = getLogger(__name__)


class IrActionsReport(models.Model):
    _inherit = "ir.actions.report"

    report_type = fields.Selection(
        selection_add=[("docx-pdf", "DOCX (PDF)")],
        ondelete={"docx-pdf": "cascade"},
    )

    def _gotenberg_base_url(self):
        ICP = self.env["ir.config_parameter"].sudo()
        base_url = ICP.get_param("gotenberg.server.url", "http://localhost:3000")
        return base_url.rstrip("/")

    def _gotenberg_convert_url(self):
        return f"{self._gotenberg_base_url()}/forms/libreoffice/convert"

    def _gotenberg_health_url(self):
        return f"{self._gotenberg_base_url()}/health"

    def _gotenberg_auth(self):
        ICP = self.env["ir.config_parameter"].sudo()
        username = ICP.get_param("gotenberg.server.username") or None
        password = ICP.get_param("gotenberg.server.password") or None
        if username and password:
            return (username, password)
        return None

    def _gotenberg_check_installed(self):
        """Проверяем, что сервис живой (GET /health)."""
        url = self._gotenberg_health_url()
        try:
            resp = get_request(url, timeout=3)
            if resp.status_code == codes_request.ok:
                return True
            _logger.warning("Gotenberg health check failed: %s - %s", resp.status_code, resp.text)
        except Exception as e:
            _logger.warning("Gotenberg health check error: %s", e)
        return False

    @api.depends("report_type", "model")
    def _compute_report_name(self):
        """Расширяем логику вычисления report_name для docx-pdf."""
        super()._compute_report_name()
        for record in self:
            if (
                record.report_type == "docx-pdf"
                and record.model
                and record.id
            ):
                record.report_name = "%s-docx_report+%s" % (record.model, record.id)

    def retrieve_attachment(self, record):
        """
        Расширяем retrieve_attachment: для docx-pdf отбираем только PDF-вложения.
        """
        result = super().retrieve_attachment(record)
        if result and self.report_type == "docx-pdf":
            result = result.filtered(lambda r: r.mimetype == "application/pdf") or None
        return result

    @api.model
    def _render_docx_pdf(self, res_ids=None, data=None):
        """
        Подготовка данных, рендер DOCX, конвертация в PDF через Gotenberg
        и сохранение/возврат результата.
        """
        if not data:
            data = {}
        data.setdefault("report_type", "pdf")

        self_sudo = self.sudo()

        save_in_attachment = OrderedDict()
        if res_ids:
            Model = self.env[self_sudo.model]
            record_ids = Model.browse(res_ids)
            docx_record_ids = Model
            if self_sudo.attachment:
                for record_id in record_ids:
                    attachment = self_sudo.retrieve_attachment(record_id)
                    if attachment and self_sudo.attachment_use:
                        stream = BytesIO(attachment.raw)
                        save_in_attachment[record_id.id] = stream
                    if not self_sudo.attachment_use or not attachment:
                        docx_record_ids += record_id
            else:
                docx_record_ids = record_ids
            res_ids = docx_record_ids.ids

        if save_in_attachment:
            _logger.info("The PDF report has been generated from attachment.")
            return self_sudo._post_pdf(save_in_attachment), "pdf"

        docx_content = self._render_docx(res_ids, data=data)

        if not self._gotenberg_check_installed():
            raise UserError(
                _(
                    "Gotenberg converting service not available. "
                    "The PDF can not be created."
                )
            )

        pdf_content = self._get_pdf_from_office(docx_content)

        if not pdf_content:
            raise UserError(
                _(
                    "Gotenberg converting service not available. "
                    "The PDF can not be created."
                )
            )

        if res_ids:
            return (
                self_sudo._post_pdf(
                    save_in_attachment, pdf_content=pdf_content, res_ids=res_ids
                ),
                "pdf",
            )

        return pdf_content, "pdf"

    def _post_pdf(self, save_in_attachment, pdf_content=None, res_ids=None):
        """
        Добавляет PDF-файл в вложения (ir.attachment).

        2 режима:
        - save_in_attachment и not res_ids: берём уже существующие streams и объединяем.
        - res_ids и pdf_content: создаём вложения на основе только что сгенерированного PDF.
        """
        self_sudo = self.sudo()
        attachment_vals_list = []

        if save_in_attachment:
            reports_data = list(save_in_attachment.values())
            if len(reports_data) == 1:
                return reports_data[0].getvalue()
            else:
                return self._merge_pdfs(reports_data)

        for res_id in res_ids or []:
            record = self.env[self_sudo.model].browse(res_id)
            if not self_sudo.attachment:
                attachment_name = False
            else:
                attachment_name = safe_eval(
                    self_sudo.attachment, {"object": record, "time": time}
                )
            if not attachment_name:
                continue
            attachment_vals_list.append(
                {
                    "name": attachment_name,
                    "raw": pdf_content,
                    "res_model": self_sudo.model,
                    "res_id": record.id,
                    "type": "binary",
                }
            )
        if attachment_vals_list:
            attachment_names = ", ".join(x["name"] for x in attachment_vals_list)
            try:
                self.env["ir.attachment"].create(attachment_vals_list)
            except AccessError:
                _logger.info(
                    "Cannot save PDF report %r attachments for user %r",
                    attachment_names,
                    self.env.user.display_name,
                )
            else:
                _logger.info(
                    "The PDF documents %r are now saved in the database",
                    attachment_names,
                )
        return pdf_content

    def _get_pdf_from_office(self, content_stream):
        """
        Конвертация DOCX в PDF через сервис Gotenberg.
        content_stream — BytesIO с docx.
        """
        result = None
        url = self._gotenberg_convert_url()
        auth = self._gotenberg_auth()
        try:
            response = post_request(
                url,
                files={"files": ("converted_file.docx", content_stream.read())},
                auth=auth,
            )
            if response.status_code == codes_request.ok:
                result = response.content
            else:
                _logger.warning(
                    "Gotenberg response: %s - %s",
                    response.status_code,
                    response.content,
                )
        except RequestException as e:
            _logger.exception(e)
        finally:
            return result
