# -*- coding: utf-8 -*-
import string
import random
import logging
import threading
from odoo import models, fields, api, registry, SUPERUSER_ID
from odoo.exceptions import UserError
from odoo.service import db
from odoo.tools import config
from odoo.modules.registry import Registry

_logger = logging.getLogger(__name__)

def _async_provisioning_worker(parent_db, saas_res_id, db_name, modules, company_vals, admin_vals, timezone, admin_pass):
    _logger.info(f"Starting async provisioning background thread for database: {db_name}")
    try:
        # 1. Run module installation via Odoo CLI subprocess to avoid registry/cache pollution
        import subprocess
        # Remove 'base' from modules list if present since exp_create_database already installs it
        cli_modules = [m for m in modules if m != 'base']
        cmd = ["odoo", "-i", ",".join(cli_modules), "-d", db_name, "--db_host=db", "-r", "odoo", "-w", "odoo", "--stop-after-init"]
        _logger.info(f"Executing background CLI installation: {' '.join(cmd)}")
        
        result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        if result.returncode != 0:
            raise Exception(f"Odoo CLI installation failed (exit code {result.returncode}): {result.stderr or result.stdout}")
            
        _logger.info(f"Background thread: Base modules installed on {db_name}. Configuring company & admin...")
        
        # 2. Configure Company and Admin User inside the new database
        tenant_registry = registry(db_name)
        with tenant_registry.cursor() as cr:
            env = api.Environment(cr, SUPERUSER_ID, {})
            
            # Company setup
            company = env['res.company'].search([], limit=1)
            company.write(company_vals)
            
            # Admin user setup (ID 2 is usually admin)
            admin_user = env['res.users'].browse(2)
            admin_user.write(admin_vals)
            
            # Set timezone
            admin_user.partner_id.tz = timezone or 'America/New_York'
            
            # Force Odoo registry signaling update
            env.registry.registry_invalidated = True
            env.registry.signal_changes()
            cr.commit()
            
        # Force a registry reload locally in memory to sync the active process immediately
        _logger.info(f"Background thread: Forcing local registry reload in memory for {db_name}...")
        Registry.new(db_name)
            
        _logger.info(f"Background thread: Configuration completed for {db_name}. Activating SaaS Master record...")
        
        # 3. Update SaaS Master record status and send Welcome Email
        parent_registry = registry(parent_db)
        with parent_registry.cursor() as cr:
            env = api.Environment(cr, SUPERUSER_ID, {})
            saas_record = env['saas.restaurant'].browse(saas_res_id)
            saas_record.write({'status': 'active'})
            
            # Send Welcome Email
            template = env.ref('dine360_saas_master.saas_welcome_email_template', raise_if_not_found=False)
            if template:
                saas_record.with_context(temp_password=admin_pass).message_post_with_source(
                    source_ref=template,
                    subtype_xmlid='mail.mt_comment'
                )
            
            # Send real-time bus notification to the backend UI to refresh the form view
            try:
                env['bus.bus']._sendone('saas_provisioning', 'saas_provisioning', {
                    'saas_record_id': saas_record.id,
                    'status': 'active'
                })
            except Exception as bus_err:
                _logger.error(f"Failed to send success bus notification: {str(bus_err)}")

            cr.commit()
            
        _logger.info(f"Background thread: Async provisioning finished successfully for {db_name}!")
        
    except Exception as e:
        _logger.error(f"Background thread: Provisioning failed for database {db_name}: {str(e)}")
        # Drop the corrupted database shell
        try:
            db.exp_drop(db_name)
        except Exception as drop_err:
            _logger.error(f"Background thread: Failed to drop database {db_name} on cleanup: {str(drop_err)}")
            
        # Update SaaS record to failed
        try:
            parent_registry = registry(parent_db)
            with parent_registry.cursor() as cr:
                env = api.Environment(cr, SUPERUSER_ID, {})
                saas_record = env['saas.restaurant'].browse(saas_res_id)
                saas_record.write({'status': 'failed'})
                saas_record.message_post(body=f"Failed background database provisioning: {str(e)}")
                
                # Send real-time bus notification to the backend UI to refresh the form view
                try:
                    env['bus.bus']._sendone('saas_provisioning', 'saas_provisioning', {
                        'saas_record_id': saas_record.id,
                        'status': 'failed'
                    })
                except Exception as bus_err:
                    _logger.error(f"Failed to send failure bus notification: {str(bus_err)}")

                cr.commit()
        except Exception as update_err:
            _logger.error(f"Background thread: Failed to mark status as failed on SaaS record: {str(update_err)}")


class SaasRestaurant(models.Model):
    _name = 'saas.restaurant'
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _description = 'Dine360 SaaS Restaurant Tenant'

    name = fields.Char(string='Restaurant Name', required=True, tracking=True)
    owner_name = fields.Char(string='Owner Name', required=True, tracking=True)
    email = fields.Char(string='Owner Email', required=True, tracking=True)
    phone = fields.Char(string='Phone')
    street = fields.Char(string='Street')
    city = fields.Char(string='City')
    country_id = fields.Many2one('res.country', string='Country')
    
    plan_id = fields.Many2one('saas.plan', string='Subscription Plan', required=True, tracking=True)
    billing_cycle = fields.Selection([
        ('monthly', 'Monthly Billing'),
        ('yearly', 'Annual Billing')
    ], string='Billing Cycle', default='monthly', required=True)
    
    database_name = fields.Char(string='PostgreSQL DB Name', readonly=True, copy=False)
    subdomain = fields.Char(string='Subdomain', readonly=True, copy=False)
    
    status = fields.Selection([
        ('draft', 'Draft'),
        ('provisioning', 'Provisioning'),
        ('active', 'Active'),
        ('suspended', 'Suspended'),
        ('expired', 'Expired'),
        ('failed', 'Failed')
    ], string='Status', default='draft', required=True, tracking=True)
    
    start_date = fields.Date(string='Start Date', default=fields.Date.context_today)
    expiry_date = fields.Date(string='Expiry Date', required=True)
    
    currency_id = fields.Many2one('res.currency', string='Base Currency')
    timezone = fields.Char(string='Timezone', default='America/New_York')
    logo = fields.Binary(string='Restaurant Logo')
    activate_table_reservation = fields.Boolean(
        string='Activate Table Reservation Theme', 
        default=False,
        help='If checked, automatically installs and configures the themed Table Reservation module on this restaurant database.'
    )

    _sql_constraints = [
        ('unique_subdomain', 'unique(subdomain)', 'This subdomain is already taken!'),
        ('unique_database_name', 'unique(database_name)', 'This database name is already registered!')
    ]

    @api.model
    def create(self, vals):
        # Generate subdomain and database name from restaurant name
        name_slug = "".join([c.lower() for c in vals.get('name', '') if c.isalnum()])
        if not name_slug:
            name_slug = "restaurant"
            
        counter = 1
        original_slug = name_slug
        while self.search([('subdomain', '=', f"{name_slug}.dine360.com")]):
            name_slug = f"{original_slug}{counter}"
            counter += 1
            
        vals['subdomain'] = f"{name_slug}.dine360.com"
        vals['database_name'] = f"dine360_restaurant_{name_slug}"
        return super(SaasRestaurant, self).create(vals)

    def action_create_database(self):
        self.ensure_one()
        if self.database_name in db.list_dbs():
            raise UserError(f"Database {self.database_name} already exists in PostgreSQL!")

        # 1. Provision PostgreSQL database
        admin_pass = ''.join(random.choices(string.ascii_letters + string.digits, k=12))
        
        try:
            _logger.info(f"Creating database {self.database_name}...")
            db.exp_create_database(
                self.database_name, 
                False, 
                'en_US', 
                user_password=admin_pass, 
                login=self.email,
                country_code=self.country_id.code if self.country_id else None,
                phone=self.phone
            )
            _logger.info(f"Database {self.database_name} created successfully.")
        except Exception as e:
            raise UserError(f"PostgreSQL database creation failed: {str(e)}")

        # 2. Extract configuration values
        company_vals = {
            'name': self.name,
            'phone': self.phone,
            'email': self.email,
            'street': self.street,
            'city': self.city,
            'country_id': self.country_id.id if self.country_id else False,
            'currency_id': self.currency_id.id if self.currency_id else False,
            'logo': self.logo,
        }
        admin_vals = {
            'name': self.owner_name,
            'login': self.email,
            'email': self.email,
            'password': admin_pass,
        }
        
        modules_to_install = [
            'base', 'contacts', 'sale', 'stock', 'purchase', 
            'account', 'point_of_sale', 'hr', 'pos_restaurant'
        ]
        if self.activate_table_reservation:
            modules_to_install.extend(['website', 'dine360_table', 'dine360_reservation', 'dine360_theme_reservation'])

        # Set status to provisioning
        self.status = 'provisioning'
        
        # 3. Spawn background worker thread
        parent_db = self.env.cr.dbname
        t = threading.Thread(
            target=_async_provisioning_worker,
            args=(parent_db, self.id, self.database_name, modules_to_install, company_vals, admin_vals, self.timezone, admin_pass),
            daemon=True
        )
        t.start()
        
        return {
            'type': 'ir.actions.client',
            'tag': 'display_notification',
            'params': {
                'title': 'Database Provisioning Started',
                'message': 'Your database creation has started in the background. Please wait 1-2 minutes for completion before logging in.',
                'sticky': False,
                'next': {'type': 'ir.actions.client', 'tag': 'reload'},
            }
        }

    def action_suspend(self):
        self.ensure_one()
        # Suspend by disabling login in target database
        tenant_registry = registry(self.database_name)
        try:
            with tenant_registry.cursor() as cr:
                env = api.Environment(cr, SUPERUSER_ID, {})
                # Disable all users except superuser
                users = env['res.users'].search([('id', '!=', SUPERUSER_ID)])
                users.write({'active': False})
                cr.commit()
            self.status = 'suspended'
        except Exception as e:
            raise UserError(f"Failed to suspend database users: {str(e)}")

    def action_activate(self):
        self.ensure_one()
        # Activate by re-enabling login in target database
        tenant_registry = registry(self.database_name)
        try:
            with tenant_registry.cursor() as cr:
                env = api.Environment(cr, SUPERUSER_ID, {})
                # Enable all users
                users = env['res.users'].search([('active', '=', False), ('id', '!=', SUPERUSER_ID)])
                users.write({'active': True})
                cr.commit()
            self.status = 'active'
        except Exception as e:
            raise UserError(f"Failed to activate database users: {str(e)}")

    @api.model
    def cron_check_subscriptions(self):
        """ Runs nightly to find expired accounts and enforce subscription plan limits """
        today = fields.Date.context_today(self)
        expired_tenants = self.search([('expiry_date', '<', today), ('status', '=', 'active')])
        for tenant in expired_tenants:
            tenant.status = 'expired'
            tenant.action_suspend()
            
        # Enforcement check for limits
        active_tenants = self.search([('status', '=', 'active')])
        for tenant in active_tenants:
            plan = tenant.plan_id
            tenant_registry = registry(tenant.database_name)
            try:
                with tenant_registry.cursor() as cr:
                    env = api.Environment(cr, SUPERUSER_ID, {})
                    
                    # 1. Count active users
                    user_count = env['res.users'].search_count([('active', '=', True), ('id', '!=', SUPERUSER_ID)])
                    # 2. Count active POS terminals
                    pos_count = env['pos.config'].search_count([('active', '=', True)])
                    
                    # Log warnings/limits
                    if plan.max_users > 0 and user_count > plan.max_users:
                        _logger.warning(f"Tenant {tenant.name} exceeds user limit: {user_count}/{plan.max_users}")
                        # Auto suspend or flag
                    if plan.max_pos > 0 and pos_count > plan.max_pos:
                        _logger.warning(f"Tenant {tenant.name} exceeds POS limit: {pos_count}/{plan.max_pos}")
            except Exception as e:
                _logger.error(f"Error checking limits for {tenant.database_name}: {str(e)}")
