# -*- coding: utf-8 -*-
# Part of ds_amcs_extract. See LICENSE file for full copyright and licensing details.

"""
HR Expense Extension
--------------------
Extends hr.expense with OCR processing capabilities using the OCR mixin.
"""

import json
import logging
import uuid
from odoo import _, api, fields, models
from odoo.exceptions import UserError

_logger = logging.getLogger(__name__)


class HrExpense(models.Model):
    """
    Override hr.expense to use custom OCR service.
    """

    _inherit = "hr.expense"

    # OCR fields (consolidated from ocr.mixin)
    ocr_extract_state = fields.Selection(
        [
            ("no_extract_requested", "No Extract Requested"),
            ("waiting_extraction", "Waiting Extraction"),
            ("extract_not_ready", "Extract Not Ready"),
            ("waiting_validation", "Waiting Validation"),
            ("to_validate", "To Validate"),
            ("done", "Done"),
        ],
        string="OCR State",
        default="no_extract_requested",
        tracking=True,
    )
    ocr_extract_request_uuid = fields.Char(string="Request UUID", copy=False)
    ocr_extract_document_id = fields.Many2one("ir.attachment", string="OCR Document", copy=False)
    ocr_extract_last_check = fields.Datetime(string="Last Status Check", copy=False)
    ocr_extract_result_json = fields.Text(string="OCR Result", copy=False)
    ocr_extract_validated_on = fields.Datetime(string="Validated On", copy=False)

    @api.constrains("ocr_extract_state")
    def _check_ocr_extract_state_transition(self):
        """Validate that state transitions follow the defined order."""
        VALID_TRANSITIONS = {
            "no_extract_requested": ["no_extract_requested", "waiting_extraction"],
            "waiting_extraction": ["waiting_extraction", "extract_not_ready", "waiting_validation"],
            "extract_not_ready": ["extract_not_ready", "waiting_validation"],
            "waiting_validation": ["waiting_validation", "to_validate"],
            "to_validate": ["to_validate", "done"],
            "done": ["done"],
        }
        for record in self:
            if not record._origin.ocr_extract_state:
                continue
            current_state = record._origin.ocr_extract_state
            if current_state not in VALID_TRANSITIONS:
                continue
            allowed = VALID_TRANSITIONS[current_state]
            if record.ocr_extract_state not in allowed:
                raise UserError(
                    _('Invalid state transition from "%s" to "%s"')
                    % (current_state, record.ocr_extract_state)
                )

    def _get_nested_value(self, data, key, default=None):
        if not isinstance(data, dict):
            return default
        value = data.get(key, default)
        if isinstance(value, dict):
            if "selected_value" in value:
                return value.get("selected_value", {}).get("content", default)
            if "value" in value:
                return value.get("value", default)
        return value if value is not None else default

    def _get_ocr_client(self):
        from .ocr_client import OCRClientFactory

        return OCRClientFactory.create(self.env)

    def send_for_ocr_extraction(self):
        """Send the document for OCR processing."""
        self.ensure_one()
        if self.ocr_extract_state != "no_extract_requested":
            raise UserError(_("Document already sent for extraction"))
        doc_b64 = self._get_document_base64()
        if not doc_b64:
            raise UserError(_("No document attached to this record"))
        try:
            client = self._get_ocr_client()
            result = client.parse(doc_b64, service_name=self._get_ocr_service_name())
            self.write({
                "ocr_extract_state": "waiting_extraction",
                "ocr_extract_request_uuid": result.get("document_token", str(uuid.uuid4()) if "uuid" not in locals() else str(uuid.uuid4())),
                "ocr_extract_last_check": fields.Datetime.now(),
            })
            if result.get("status") == "success":
                extraction_data = result.get("result") or result.get("results", [{}])[0]
                self.ocr_extract_result_json = json.dumps(extraction_data)
                self.ocr_extract_state = "waiting_validation"
                self._process_ocr_result(extraction_data)
        except Exception as e:
            _logger.error("Failed to send for OCR: %s", str(e))
            raise UserError(_("Failed to send document for OCR: %s") % str(e))
        return True

    def check_ocr_status(self):
        """Check the status of a pending OCR extraction."""
        self.ensure_one()
        if self.ocr_extract_state not in ["waiting_extraction", "extract_not_ready"]:
            return False
        if not self.ocr_extract_request_uuid:
            return False
        try:
            client = self._get_ocr_client()
            result = client.get_result(self.ocr_extract_request_uuid)
            self.ocr_extract_last_check = fields.Datetime.now()
            if result.get("status") == "ready" or result.get("status") == "success":
                extraction_data = result.get("result", {})
                self.ocr_extract_result_json = json.dumps(extraction_data)
                self.ocr_extract_state = "waiting_validation"
                self._process_ocr_result(extraction_data)
                return True
            elif result.get("status") == "pending":
                if self.ocr_extract_state == "waiting_extraction":
                    self.ocr_extract_state = "extract_not_ready"
        except Exception as e:
            _logger.exception("Error checking OCR status for %s: %s", self.id, str(e))
        return False

    def _get_document_base64(self):
        """Get document data from expense attachment as base64 string."""
        attachment = self.message_main_attachment_id
        if attachment:
            datas = attachment.datas
            if isinstance(datas, bytes):
                return datas.decode("utf-8")
            return datas

        for att in self.attachment_ids:
            datas = att.datas
            if isinstance(datas, bytes):
                return datas.decode("utf-8")
            return datas

        return None

    def _get_ocr_service_name(self):
        """Get the OCR service name for receipts."""
        return "receipt_ocr"

    @api.model_create_multi
    def create(self, vals_list):
        """Override create to set default employee if not provided."""
        for vals in vals_list:
            if not vals.get("employee_id"):
                employee = self.env["hr.employee"].search(
                    [("user_id", "=", self.env.uid)], limit=1
                )
                if employee:
                    vals["employee_id"] = employee.id
                else:
                    _logger.warning(
                        "OCR Expense: No employee found for user %s", self.env.uid
                    )

        return super(HrExpense, self).create(vals_list)

    def _process_ocr_result(self, result):
        """
        Process OCR result and apply to expense.
        """
        _logger.info("Processing OCR result for expense %s", self.id)

        if isinstance(result, dict) and "results" in result:
            if isinstance(result["results"], list) and result["results"]:
                result = result["results"][0]

        for expense in self:
            vals = {}

            date_val = self._get_nested_value(result, "date")
            if date_val and not expense.date:
                vals["date"] = date_val

            total_val = self._get_nested_value(result, "total")
            if total_val and not expense.total_amount:
                try:
                    vals["total_amount"] = float(total_val)
                except (ValueError, TypeError):
                    pass

            supplier = self._get_nested_value(
                result, "supplier_name"
            ) or self._get_nested_value(result, "supplier")
            ref = self._get_nested_value(result, "invoice_id")

            if not expense.name or expense.name == "/":
                if supplier and ref:
                    vals["name"] = f"{supplier} - {ref}"
                elif supplier:
                    vals["name"] = supplier
                elif ref:
                    vals["name"] = ref

            currency_code = self._get_nested_value(result, "currency")
            if currency_code and not expense.currency_id:
                currency = self.env["res.currency"].search(
                    [("name", "=", currency_code)], limit=1
                )
                if currency:
                    vals["currency_id"] = currency.id

            if not expense.employee_id:
                employee = self.env["hr.employee"].search(
                    [("user_id", "=", self.env.uid)], limit=1
                )
                if employee:
                    vals["employee_id"] = employee.id

            if vals:
                _logger.info(
                    "OCR Expense Mapping: Writing to expense %s: %s", expense.id, vals
                )
                expense.write(vals)

    def _save_form(self, *args, **kwargs):
        """Override _save_form for expense OCR handling."""
        res = super()._save_form(*args, **kwargs)

        try:
            ocr_results = args[0] if args else kwargs.get("ocr_results", {})
            if not ocr_results:
                return res

            self._process_ocr_result(ocr_results)

        except Exception as e:
            _logger.error("OCR Expense: Failed _save_form processing: %s", e)

        return res

    @api.model
    def _contact_iap_extract(self, pathinfo, params):
        """
        Redirect standard Odoo OCR calls to the custom local server.
        """
        client = self._get_ocr_client()
        if pathinfo == "parse":
            if not params.get("documents"):
                return {"status": "error_no_document"}
            doc_b64 = params["documents"][0]
            user_infos = {
                "company_name": self.env.company.name if self.env.company else "",
                "currency": self.env.company.currency_id.name if self.env.company else "",
            }
            result = client.parse(doc_b64, service_name="receipt_ocr", user_infos=user_infos)
            if result.get("status") == "success":
                return result
            return {"status": "error", "message": result.get("message", "Unknown error")}
        elif pathinfo == "get_result":
            if not params.get("uuid"):
                return {"status": "error_no_uuid"}
            return client.get_result(params["uuid"])
        return {"status": "success"}

    @api.model
    def check_all_expense_status(self):
        """
        Cron job method to check status of pending expense extractions.
        """
        expenses = self.search(
            [
                ("ocr_extract_state", "in", ["waiting_extraction", "extract_not_ready"]),
            ],
            limit=20,
        )

        if not expenses:
            return 0

        _logger.info("OCR cron: checking %d expense(s)", len(expenses))

        processed = 0
        for expense in expenses:
            try:
                if expense.check_ocr_status():
                    processed += 1
            except Exception as e:
                _logger.exception(
                    "OCR cron: error checking expense %s: %s", expense.id, e
                )

        _logger.info("OCR cron: processed %d expense extractions", processed)
        return processed
