import json
import re

from odoo import http
from odoo.addons.web.controllers.export import ExportXlsxWriter
from odoo.http import content_disposition, request


def _split_field_path(fieldname):
    """_export_rows() (unlike export_data()) takes each field already split
    on '/' into path segments, e.g. "employee_id/xeno_employee_code" ->
    ["employee_id", "xeno_employee_code"] -- export_data() does this same
    split (odoo/orm/models.py's own fix_import_export_id_paths) before
    calling _export_rows() internally; since we call _export_rows()
    directly to skip export_data()'s group_allow_export check, that split
    has to be reproduced here too, or a flat dotted/slashed field name gets
    iterated character-by-character instead ("Invalid field 'e'")."""
    fixed_db_id = re.sub(r"([^/])\.id", r"\1/.id", fieldname)
    fixed_external_id = re.sub(r"([^/]):id", r"\1/id", fixed_db_id)
    return fixed_external_id.split("/")

# Core's own /web/export/xlsx additionally gates export on
# base.group_allow_export (env['model'].export_data()'s own check) -- a
# broad, model-agnostic "Allow Export" technical right that regular HR
# employees don't have by default, even though they already have full read
# access to hr.leave/hr.leave.allocation via this module's own Leave List /
# Leave Balances pages, and hit "You don't have the rights to export data"
# clicking those pages' own Export/Template buttons. _export_rows() does the
# exact same field-formatting/traversal work as export_data() with no such
# gate; ExportXlsxWriter is the same class core's controller uses, so the
# output stays byte-identical (dates/many2one/selection labels formatted the
# same way). Normal ACL/record-rule read access is still fully enforced --
# only the separate, all-or-nothing "Allow Export" right is skipped.
class XenoExportXlsx(http.Controller):

    @http.route("/xeno_leave/export_xlsx", type="http", auth="user")
    def export_xlsx(self, data):
        payload = json.loads(data)
        model = payload["model"]
        fields = payload["fields"]
        ids = payload.get("ids") or []
        domain = payload.get("domain") or []
        filename = payload.get("filename") or "export.xlsx"

        Model = request.env[model]
        records = Model.browse(ids) if ids else Model.search(domain)
        rows = records._export_rows([_split_field_path(f["name"]) for f in fields])
        headers = [f["label"] for f in fields]

        with ExportXlsxWriter(fields, headers, len(rows)) as writer:
            for row_index, row in enumerate(rows):
                for col_index, cell in enumerate(row):
                    writer.write_cell(row_index + 1, col_index, cell)
        # .value is only populated by close() (__exit__), so it must be
        # read after the `with` block ends -- reading it inside returns
        # __init__'s placeholder (False) instead of the finished xlsx
        # bytes, which is what produced the "'bool' object is not
        # iterable" 500 the first time this shipped.
        xlsx_data = writer.value

        return request.make_response(
            xlsx_data,
            headers=[
                ("Content-Type",
                 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
                ("Content-Disposition", content_disposition(filename)),
            ],
        )
