import re
import frappe
from frappe.model.document import Document
from frappe import _

# Import Incus API functions from incus_manager
try:
    from incus_manager.incus_manager.doctype.incus_container.incus_container import (
        create_container as incus_create_container,
        list_containers as incus_list_containers,
    )
    INCUS_AVAILABLE = True
except ImportError:
    INCUS_AVAILABLE = False

# Container name validation: lowercase letters, numbers, hyphens, 3-63 chars, starts with letter
CONTAINER_NAME_PATTERN = re.compile(r'^[a-z][a-z0-9-]{2,62}$')


class CloudContainer(Document):
    """DocType for managing user cloud containers"""

    def validate(self):
        """Validate container data before save"""
        self.validate_container_name()

    def validate_container_name(self):
        """Validate container name format"""
        if not self.container_name:
            frappe.throw(_("Container name is required"))

        if not CONTAINER_NAME_PATTERN.match(self.container_name):
            frappe.throw(
                _("Container name must be 3-63 characters, start with a letter, "
                  "and contain only lowercase letters, numbers, and hyphens")
            )

    def before_insert(self):
        """Set owner to current user before insert"""
        if not self.owner:
            self.owner = frappe.session.user

    def after_insert(self):
        """Queue container creation in Incus after document is saved"""
        if self.status == "Pending":
            frappe.enqueue(
                "cloud_portal.cloud_portal.doctype.cloud_container.cloud_container.create_incus_container",
                queue="default",
                timeout=300,
                doc_name=self.name,
                container_name=self.container_name,
                image=self.image,
                container_type=self.container_type
            )
            self.db_set("status", "Creating")


def create_incus_container(doc_name, container_name, image, container_type):
    """Background job to create container in Incus"""
    # Get owner for realtime notification
    owner = frappe.db.get_value("Cloud Container", doc_name, "owner")

    if not INCUS_AVAILABLE:
        frappe.db.set_value("Cloud Container", doc_name, {
            "status": "Error",
            "error_message": "Incus Manager module not available"
        })
        frappe.db.commit()
        _notify_user(owner)
        return

    try:
        # Send request without waiting - trust it will be created
        result = incus_create_container(
            name=container_name,
            image_alias=image,
            container_type=container_type,
            wait_completion=False  # Don't wait - avoid round-robin issues
        )

        if result.get("success") or result.get("operation_url"):
            # Request accepted - consider success, status will be updated during sync
            frappe.db.set_value("Cloud Container", doc_name, {
                "status": "Running",  # Optimistically set to Running
                "incus_status": "Created",
                "error_message": ""
            })
        else:
            frappe.db.set_value("Cloud Container", doc_name, {
                "status": "Error",
                "error_message": result.get("message", result.get("error", "Unknown error"))
            })

        frappe.db.commit()
        _notify_user(owner)

    except Exception as e:
        frappe.db.set_value("Cloud Container", doc_name, {
            "status": "Error",
            "error_message": str(e)
        })
        frappe.db.commit()
        _notify_user(owner)


def _notify_user(user):
    """Send realtime notification to user about container list update"""
    if user:
        frappe.publish_realtime(
            "list_update",
            {"doctype": "Cloud Container"},
            user=user
        )


@frappe.whitelist()
def get_user_containers():
    """
    Get containers for current user only.
    Explicit owner filter ensures users (including admins) only see their own containers.
    """
    return frappe.get_all(
        "Cloud Container",
        filters={"owner": frappe.session.user},
        fields=["name", "container_name", "image", "container_type",
                "status", "incus_status", "ip_address", "creation"],
        order_by="creation desc"
    )


@frappe.whitelist()
def create_user_container(container_name, image="alpine/edge", container_type="container",
                          max_users=1, trial=0):
    """
    Create a new container for current user.
    Validation is handled by DocType's validate() method.
    Permissions are handled by DocType permissions (Website User with if_owner).
    """
    max_users = int(max_users)
    trial = int(trial)

    doc = frappe.get_doc({
        "doctype": "Cloud Container",
        "container_name": container_name,
        "image": image,
        "container_type": container_type,
        "status": "Pending",
        "max_users": 1 if trial else max_users,
        "trial": trial,
    })

    doc.insert()

    # Create ERPNext Subscription
    subscription_name = _create_subscription(doc, max_users=1 if trial else max_users, trial=trial)
    if subscription_name:
        frappe.db.set_value("Cloud Container", doc.name, "subscription", subscription_name)
        frappe.db.commit()

        # Push subscription to remote ERPNext (async to avoid blocking the user)
        frappe.enqueue(
            "cloud_portal.api.erpnext_sync.push_and_store_remote_subscription",
            queue="default",
            timeout=30,
            container_name=doc.name,
            local_sub_name=subscription_name,
        )

    return {
        "success": True,
        "name": doc.name,
        "container_name": doc.container_name,
        "message": _("Container creation started")
    }


# ---------------------------------------------------------------------------
# Subscription helpers
# ---------------------------------------------------------------------------

PLAN_NAME = "Cloud Container - Per User"
ITEM_CODE = "Cloud Container User License"


def _create_subscription(container_doc, max_users, trial):
    """Create an ERPNext Subscription linked to the current user's Customer."""
    user = frappe.session.user

    customer = frappe.db.get_value("Portal User", {"user": user}, "parent")
    if not customer:
        frappe.log_error(f"No customer found for user {user}", "Subscription Creation")
        return None

    plan = _get_or_create_subscription_plan()
    if not plan:
        return None

    today = frappe.utils.today()
    company = frappe.db.get_single_value("Global Defaults", "default_company")

    sub = frappe.get_doc({
        "doctype": "Subscription",
        "party_type": "Customer",
        "party": customer,
        "start_date": today,
        "generate_invoice_at": "End of the current subscription period",
        "plans": [{"plan": plan, "qty": max_users}],
        "company": company,
    })

    if trial:
        sub.trial_period_start = today
        sub.trial_period_end = frappe.utils.add_days(today, 14)

    try:
        sub.flags.ignore_permissions = True
        sub.insert()
        frappe.db.commit()
        return sub.name
    except Exception as e:
        frappe.log_error(f"Failed to create subscription: {e}", "Subscription Creation")
        return None


def _get_or_create_subscription_plan():
    """Get or create the per-user monthly subscription plan."""
    if frappe.db.exists("Subscription Plan", PLAN_NAME):
        return PLAN_NAME

    item = _get_or_create_service_item()
    if not item:
        return None

    currency = frappe.db.get_single_value("Global Defaults", "default_currency") or "USD"

    try:
        plan = frappe.get_doc({
            "doctype": "Subscription Plan",
            "plan_name": PLAN_NAME,
            "item": item,
            "currency": currency,
            "price_determination": "Fixed Rate",
            "cost": 0,
            "billing_interval": "Month",
            "billing_interval_count": 1,
        })
        plan.flags.ignore_permissions = True
        plan.insert()
        frappe.db.commit()
        return PLAN_NAME
    except Exception as e:
        frappe.log_error(f"Failed to create subscription plan: {e}", "Subscription Setup")
        return None


def _get_or_create_service_item():
    """Get or create the service Item for user licenses."""
    if frappe.db.exists("Item", ITEM_CODE):
        return ITEM_CODE

    item_group = next(
        (g for g in ["Services", "All Item Groups", "Products"]
         if frappe.db.exists("Item Group", g)),
        None
    )
    if not item_group:
        frappe.log_error("No suitable Item Group found", "Subscription Setup")
        return None

    try:
        item = frappe.get_doc({
            "doctype": "Item",
            "item_code": ITEM_CODE,
            "item_name": ITEM_CODE,
            "item_group": item_group,
            "is_sales_item": 1,
            "is_stock_item": 0,
        })
        item.flags.ignore_permissions = True
        item.insert()
        frappe.db.commit()
        return ITEM_CODE
    except Exception as e:
        frappe.log_error(f"Failed to create item: {e}", "Subscription Setup")
        return None


def sync_all_container_statuses():
    """Scheduled job: Sync all container statuses from Incus."""
    if not INCUS_AVAILABLE:
        return

    try:
        # Get container list from Incus
        result = incus_list_containers()

        if not result.get("success"):
            frappe.log_error(f"Failed to list containers: {result.get('error')}", "Container Sync")
            return

        incus_containers = {c["name"]: c for c in result.get("containers", [])}

        # Get all containers from DocType
        cloud_containers = frappe.get_all(
            "Cloud Container",
            filters={"status": ["not in", ["Error", "Pending"]]},
            fields=["name", "container_name", "status", "owner"]
        )

        # Map Incus status to Cloud Container status
        status_map = {"running": "Running", "stopped": "Stopped"}
        updated_owners = set()

        for cc in cloud_containers:
            container_name = cc["container_name"]
            incus_data = incus_containers.get(container_name)

            if not incus_data:
                continue

            incus_status = incus_data.get("status", "").lower()
            new_status = status_map.get(incus_status, cc["status"])

            # Only update if status changed
            if new_status != cc["status"]:
                frappe.db.set_value("Cloud Container", cc["name"], {
                    "status": new_status,
                    "incus_status": incus_data.get("status"),
                    "architecture": incus_data.get("architecture")
                })
                updated_owners.add(cc["owner"])

        frappe.db.commit()

        # Notify users about updates via realtime
        for owner in updated_owners:
            frappe.publish_realtime(
                "list_update",
                {"doctype": "Cloud Container"},
                user=owner
            )

    except Exception as e:
        frappe.log_error(f"Container sync error: {str(e)}", "Container Sync")
