# -*- coding: utf-8 -*-
import json
import logging

import requests

from odoo import _, api, fields, models
from odoo.exceptions import UserError

_logger = logging.getLogger(__name__)

DEFAULT_OPENAI_BASE_URL = 'https://api.openai.com/v1'
DEFAULT_BOT_NAME = 'Udi'


class JbAiConversation(models.Model):
    _name = 'jb.ai.conversation'
    _description = 'AI Chat Conversation'
    _order = 'write_date desc, id desc'

    name = fields.Char(string='Title', required=True, default='New chat')
    user_id = fields.Many2one(
        'res.users',
        string='Owner',
        required=True,
        default=lambda self: self.env.user,
        index=True,
    )
    model_id = fields.Many2one(
        'jb.ai.model',
        string='Model',
        required=True,
        ondelete='restrict',
    )
    message_ids = fields.One2many(
        'jb.ai.message',
        'conversation_id',
        string='Messages',
    )
    message_count = fields.Integer(compute='_compute_message_count')
    memory = fields.Text(
        string='Session Memory',
        help='Persistent notes for this chat. Sent with every message so the assistant keeps context.',
    )
    active = fields.Boolean(default=True)

    @api.depends('message_ids')
    def _compute_message_count(self):
        for conv in self:
            conv.message_count = len(conv.message_ids)

    # ------------------------------------------------------------------
    # Config helpers
    # ------------------------------------------------------------------

    @api.model
    def _get_config(self):
        icp = self.env['ir.config_parameter'].sudo()
        return {
            'api_key': icp.get_param('jb_ai_assistant.openai_api_key', ''),
            'base_url': (
                icp.get_param('jb_ai_assistant.openai_base_url', DEFAULT_OPENAI_BASE_URL)
                or DEFAULT_OPENAI_BASE_URL
            ).rstrip('/'),
            'temperature': float(icp.get_param('jb_ai_assistant.temperature', '0.7') or 0.7),
            'max_tokens': int(icp.get_param('jb_ai_assistant.max_tokens', '2048') or 2048),
            'system_prompt': icp.get_param('jb_ai_assistant.system_prompt', '') or '',
        }

    @api.model
    def _get_user_bot_name(self):
        name = (self.env.user.ai_bot_name or '').strip()
        return name or DEFAULT_BOT_NAME

    def _prepare_chat_data(self, include_messages=False):
        self.ensure_one()
        data = {
            'id': self.id,
            'name': self.name,
            'model_id': self.model_id.id,
            'model_name': self.model_id.name,
            'write_date': fields.Datetime.to_string(self.write_date),
            'message_count': self.message_count,
            'memory': self.memory or '',
        }
        if include_messages:
            data['messages'] = [msg._prepare_chat_data() for msg in self.message_ids]
        return data

    # ------------------------------------------------------------------
    # OpenAI
    # ------------------------------------------------------------------

    @api.model
    def _call_openai(self, messages, technical_name):
        """POST to OpenAI chat completions and return assistant text."""
        config = self._get_config()
        if not config['api_key']:
            raise UserError(_(
                'OpenAI API key is not configured. '
                'Ask your administrator to set it in Settings → AI Assistant.'
            ))

        url = '%s/chat/completions' % config['base_url']
        headers = {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer %s' % config['api_key'],
        }
        payload = {
            'model': technical_name,
            'messages': messages,
            'temperature': config['temperature'],
            'max_tokens': config['max_tokens'],
        }

        try:
            response = requests.post(
                url,
                data=json.dumps(payload),
                headers=headers,
                timeout=60,
            )
            response.raise_for_status()
            data = response.json()
            choices = data.get('choices') or []
            if not choices:
                raise UserError(_('OpenAI returned an empty response.'))
            content = (choices[0].get('message') or {}).get('content') or ''
            if not content.strip():
                raise UserError(_('OpenAI returned an empty message.'))
            return content.strip()
        except requests.exceptions.Timeout:
            raise UserError(_('OpenAI request timed out. Please try again.')) from None
        except requests.exceptions.HTTPError as exc:
            detail = ''
            try:
                detail = exc.response.json().get('error', {}).get('message', '')
            except Exception:
                detail = exc.response.text if exc.response is not None else str(exc)
            _logger.warning('jb_ai_assistant: OpenAI HTTP error: %s', detail)
            raise UserError(_('OpenAI error: %s') % (detail or str(exc))) from None
        except requests.exceptions.RequestException as exc:
            _logger.warning('jb_ai_assistant: OpenAI request failed: %s', exc)
            raise UserError(_('Could not reach OpenAI: %s') % exc) from None

    def _build_system_content(self):
        """Compose system prompt: settings + card rails + identity + session memory."""
        self.ensure_one()
        config = self._get_config()
        bot_name = self.user_id.ai_bot_name or DEFAULT_BOT_NAME
        parts = []

        base_prompt = (config.get('system_prompt') or '').strip()
        if base_prompt:
            parts.append(base_prompt)
        else:
            parts.append(_(
                'You are %(name)s, a helpful AI assistant for %(user)s at %(company)s. '
                'Be concise, professional, and helpful.',
                name=bot_name,
                user=self.user_id.name,
                company=self.env.company.name,
            ))

        rails = self.env['jb.ai.card.rail'].sudo().search(
            [('active', '=', True)],
            order='sequence, id',
        )
        if rails:
            rail_lines = ['## Card rails']
            for rail in rails:
                rail_lines.append('### %s\n%s' % (rail.name, (rail.content or '').strip()))
            parts.append('\n\n'.join(rail_lines))

        memory = (self.memory or '').strip()
        if memory:
            parts.append(_(
                '## Session memory\n'
                'The following notes belong to this conversation only. '
                'Treat them as established context and keep building on them:\n\n%(memory)s',
                memory=memory,
            ))

        return '\n\n'.join(part for part in parts if part)

    def _build_openai_messages(self):
        """Build message list for OpenAI from stored conversation."""
        self.ensure_one()
        result = [{'role': 'system', 'content': self._build_system_content()}]
        for msg in self.message_ids:
            if msg.role in ('user', 'assistant'):
                result.append({'role': msg.role, 'content': msg.body})
        return result

    def _auto_title_from_message(self, body):
        """Derive a short conversation title from the first user message."""
        text = (body or '').strip().replace('\n', ' ')
        if len(text) > 48:
            return text[:45] + '...'
        return text or _('New chat')

    # ------------------------------------------------------------------
    # Hub RPC API (called from OWL client action)
    # ------------------------------------------------------------------

    @api.model
    def get_bootstrap(self):
        """Return initial data for the chat client."""
        models = self.env['jb.ai.model'].search([('active', '=', True)], order='sequence, id')
        default_model = self.env['jb.ai.model'].get_default_model()
        conversations = self.search([('active', '=', True)], limit=50)
        config = self._get_config()
        return {
            'bot_name': self._get_user_bot_name(),
            'user_name': self.env.user.name,
            'models': [m._prepare_chat_data() for m in models],
            'default_model_id': default_model.id if default_model else False,
            'conversations': [c._prepare_chat_data() for c in conversations],
            'api_configured': bool(config['api_key']),
        }

    @api.model
    def get_conversation(self, conversation_id):
        """Return a single conversation with messages."""
        conv = self.browse(conversation_id)
        conv.ensure_one()
        return conv._prepare_chat_data(include_messages=True)

    @api.model
    def create_conversation(self, model_id=None):
        """Start a new empty conversation."""
        if model_id:
            model = self.env['jb.ai.model'].browse(model_id)
            if not model.exists() or not model.active:
                raise UserError(_('Selected model is not available.'))
        else:
            model = self.env['jb.ai.model'].get_default_model()
            if not model:
                raise UserError(_('No AI models are configured. Contact your administrator.'))
        conv = self.create({
            'name': _('New chat'),
            'user_id': self.env.user.id,
            'model_id': model.id,
        })
        return conv._prepare_chat_data()

    @api.model
    def rename_conversation(self, conversation_id, name):
        """Rename a conversation."""
        conv = self.browse(conversation_id)
        conv.ensure_one()
        title = (name or '').strip() or _('New chat')
        conv.write({'name': title})
        return conv._prepare_chat_data()

    @api.model
    def update_conversation_memory(self, conversation_id, memory):
        """Persist session memory for a conversation."""
        conv = self.browse(conversation_id)
        conv.ensure_one()
        conv.write({'memory': memory or ''})
        return conv._prepare_chat_data()

    @api.model
    def delete_conversation(self, conversation_id):
        """Archive a conversation (soft delete)."""
        conv = self.browse(conversation_id)
        conv.ensure_one()
        conv.write({'active': False})
        return True

    @api.model
    def send_message(self, conversation_id, body):
        """Store user message, call OpenAI, store and return assistant reply."""
        conv = self.browse(conversation_id)
        conv.ensure_one()
        text = (body or '').strip()
        if not text:
            raise UserError(_('Message cannot be empty.'))

        is_first_message = not conv.message_ids
        self.env['jb.ai.message'].create({
            'conversation_id': conv.id,
            'role': 'user',
            'body': text,
        })
        if is_first_message:
            conv.write({'name': conv._auto_title_from_message(text)})

        openai_messages = conv._build_openai_messages()
        assistant_text = self._call_openai(
            openai_messages,
            conv.model_id.technical_name,
        )
        assistant_msg = self.env['jb.ai.message'].create({
            'conversation_id': conv.id,
            'role': 'assistant',
            'body': assistant_text,
        })
        return {
            'conversation': conv._prepare_chat_data(),
            'user_message': conv.message_ids.filtered(lambda m: m.role == 'user')[-1]._prepare_chat_data(),
            'assistant_message': assistant_msg._prepare_chat_data(),
        }
