"""
Sync customer subscriptions between local ERPNext and remote ERPNext.

Configuration (site_config.json keys):
  erpnext_sync_url        – base URL of remote ERPNext, e.g. https://api.host.jeyerp.az
  erpnext_sync_api_key    – API key of remote ERPNext user
  erpnext_sync_api_secret – API secret of remote ERPNext user
"""

import frappe
import requests
from frappe import _
from urllib.parse import quote as url_quote


# ---------------------------------------------------------------------------
# Config / session helpers
# ---------------------------------------------------------------------------

def _get_config():
    conf = frappe.conf
    return {
        "base_url": conf.get("erpnext_sync_url", ""),
        "api_key": conf.get("erpnext_sync_api_key", ""),
        "api_secret": conf.get("erpnext_sync_api_secret", ""),
        # Company name in remote ERPNext (may differ from local)
        "remote_company": conf.get("erpnext_sync_company", ""),
    }


def _get_session():
    """Return (requests.Session, base_url, config) or (None, None, None) if not configured."""
    config = _get_config()
    if not config["base_url"] or not config["api_key"] or not config["api_secret"]:
        return None, None, None

    session = requests.Session()
    session.auth = (config["api_key"], config["api_secret"])
    session.headers.update({"Content-Type": "application/json"})
    return session, config["base_url"].rstrip("/"), config


def _get_remote_company(session, base_url, config):
    """Return the company name to use in the remote ERPNext."""
    if config.get("remote_company"):
        return config["remote_company"]

    # Auto-detect from remote Global Defaults
    try:
        resp = session.get(
            f"{base_url}/api/method/frappe.client.get_value",
            params={"doctype": "Global Defaults", "fieldname": "default_company"},
            timeout=10,
        )
        resp.raise_for_status()
        company = resp.json().get("message", {}).get("default_company", "")
        if company:
            frappe.logger().info(f"Auto-detected remote company: {company}")
        return company
    except Exception as e:
        frappe.log_error(f"Could not detect remote company: {e}", "Subscription Sync")
        return ""


# ---------------------------------------------------------------------------
# Core sync functions
# ---------------------------------------------------------------------------

def push_subscription_to_remote(local_sub_name):
    """
    Create a mirror of the local Subscription in remote ERPNext.
    Returns the remote subscription name (str) on success, None on failure.
    """
    session, base_url, config = _get_session()
    if not session:
        frappe.log_error("ERPNext sync credentials not configured in site_config", "Subscription Sync")
        return None

    try:
        sub = frappe.get_doc("Subscription", local_sub_name)
    except Exception as e:
        frappe.log_error(f"Cannot load local subscription {local_sub_name}: {e}", "Subscription Sync")
        return None

    plans = [{"plan": p.plan, "qty": p.qty} for p in (sub.plans or [])]
    if not plans:
        frappe.log_error(f"Subscription {local_sub_name} has no plans – skipping push", "Subscription Sync")
        return None

    remote_company = _get_remote_company(session, base_url, config)
    if not remote_company:
        frappe.log_error("Cannot determine remote company name", "Subscription Sync")
        return None

    # Ensure the customer exists in remote before creating the subscription
    if not ensure_customer_in_remote(sub.party):
        frappe.log_error(
            f"Cannot push subscription {local_sub_name}: customer '{sub.party}' not found in remote and could not be created",
            "Subscription Sync"
        )
        return None

    payload = {
        "party_type": sub.party_type or "Customer",
        "party": sub.party,
        "start_date": str(sub.start_date) if sub.start_date else frappe.utils.today(),
        "generate_invoice_at": sub.generate_invoice_at or "End of the current subscription period",
        "plans": plans,
        "company": remote_company,
    }

    if sub.trial_period_start:
        payload["trial_period_start"] = str(sub.trial_period_start)
    if sub.trial_period_end:
        payload["trial_period_end"] = str(sub.trial_period_end)

    try:
        resp = session.post(
            f"{base_url}/api/resource/Subscription",
            json=payload,
            timeout=15,
        )
        resp.raise_for_status()
        remote_name = resp.json().get("data", {}).get("name")
        if remote_name:
            frappe.logger().info(f"Pushed subscription {local_sub_name} → remote {remote_name}")
        return remote_name
    except requests.HTTPError as e:
        body = ""
        try:
            body = e.response.text[:500]
        except Exception:
            pass
        frappe.log_error(
            f"HTTP error pushing subscription {local_sub_name}: {e}\n{body}",
            "Subscription Sync"
        )
        return None
    except Exception as e:
        frappe.log_error(f"Error pushing subscription {local_sub_name}: {e}", "Subscription Sync")
        return None


def pull_subscription_status(local_sub_name, remote_sub_name):
    """
    Fetch the subscription status from remote ERPNext and update local if changed.
    Returns False if the remote subscription no longer exists (caller should re-push).
    """
    session, base_url, _ = _get_session()
    if not session:
        return True

    try:
        resp = session.get(
            f"{base_url}/api/resource/Subscription/{remote_sub_name}",
            timeout=10,
        )

        if resp.status_code == 404:
            # Remote subscription was deleted – signal caller to re-push
            return False

        resp.raise_for_status()
        remote_status = resp.json().get("data", {}).get("status")

        if not remote_status:
            return True

        local_status = frappe.db.get_value("Subscription", local_sub_name, "status")
        if remote_status != local_status:
            frappe.db.set_value("Subscription", local_sub_name, "status", remote_status)
            frappe.db.commit()
            frappe.logger().info(
                f"Subscription {local_sub_name} status updated: {local_status} → {remote_status}"
            )
        return True
    except Exception as e:
        frappe.log_error(
            f"Error pulling status for {local_sub_name} (remote: {remote_sub_name}): {e}",
            "Subscription Sync"
        )
        return True


# ---------------------------------------------------------------------------
# Customer sync
# ---------------------------------------------------------------------------

def ensure_customer_in_remote(customer_name):
    """
    Ensure the Customer exists in remote ERPNext.
    Creates it if missing. Returns True on success (exists or created), False on failure.
    """
    session, base_url, _ = _get_session()
    if not session:
        return False

    # Check if already exists
    try:
        resp = session.get(
            f"{base_url}/api/resource/Customer/{url_quote(customer_name)}",
            timeout=10,
        )
        if resp.status_code == 200:
            return True  # Already exists
    except Exception:
        pass

    # Create customer in remote
    try:
        customer = frappe.get_doc("Customer", customer_name)
        payload = {
            "customer_name": customer.customer_name,
            "customer_type": customer.customer_type or "Individual",
        }
        if customer.tax_id:
            payload["tax_id"] = customer.tax_id

        resp = session.post(
            f"{base_url}/api/resource/Customer",
            json=payload,
            timeout=15,
        )
        resp.raise_for_status()
        frappe.logger().info(f"Created customer '{customer_name}' in remote ERPNext")
        return True
    except Exception as e:
        frappe.log_error(f"Failed to create customer '{customer_name}' in remote: {e}", "Subscription Sync")
        return False


# ---------------------------------------------------------------------------
# DocType event hooks
# ---------------------------------------------------------------------------

def on_customer_insert(doc, method=None):
    """Frappe hook: sync new Customer to remote ERPNext after insert."""
    frappe.enqueue(
        "cloud_portal.api.erpnext_sync.ensure_customer_in_remote",
        queue="short",
        timeout=30,
        customer_name=doc.name,
    )


def on_subscription_insert(doc, method=None):
    """Frappe hook: sync new Subscription to remote ERPNext after insert."""
    frappe.enqueue(
        "cloud_portal.api.erpnext_sync.push_and_store_remote_subscription_by_name",
        queue="default",
        timeout=60,
        local_sub_name=doc.name,
    )


# ---------------------------------------------------------------------------
# Background job helpers
# ---------------------------------------------------------------------------

def push_and_store_remote_subscription(container_name, local_sub_name):
    """
    Background job: push a newly created subscription to remote ERPNext
    and store the remote ID on both the Cloud Container and the Subscription.
    """
    remote_sub_name = push_subscription_to_remote(local_sub_name)
    if remote_sub_name:
        frappe.db.set_value("Cloud Container", container_name, "remote_subscription", remote_sub_name)
        _store_remote_sub_on_subscription(local_sub_name, remote_sub_name)
        frappe.db.commit()


def push_and_store_remote_subscription_by_name(local_sub_name):
    """
    Background job (called from on_subscription_insert hook):
    push a newly created subscription to remote ERPNext and store the remote ID.
    Skips if already synced.
    """
    # Avoid double-push if already handled by push_and_store_remote_subscription
    existing = frappe.db.get_value("Subscription", local_sub_name, "custom_remote_sub_id")
    if existing:
        return

    remote_sub_name = push_subscription_to_remote(local_sub_name)
    if remote_sub_name:
        _store_remote_sub_on_subscription(local_sub_name, remote_sub_name)
        # Also update Cloud Container if linked
        container = frappe.db.get_value("Cloud Container", {"subscription": local_sub_name}, "name")
        if container:
            frappe.db.set_value("Cloud Container", container, "remote_subscription", remote_sub_name)
        frappe.db.commit()


def _store_remote_sub_on_subscription(local_sub_name, remote_sub_name):
    """Store the remote subscription ID in a custom field on the local Subscription."""
    try:
        frappe.db.set_value("Subscription", local_sub_name, "custom_remote_sub_id", remote_sub_name)
    except Exception:
        # Custom field might not exist yet — safe to ignore, Container field is the fallback
        pass


# ---------------------------------------------------------------------------
# Scheduled job
# ---------------------------------------------------------------------------

def sync_all_subscriptions():
    """
    Scheduled job: push any un-synced subscriptions and pull status updates
    from remote ERPNext for all already-synced subscriptions.
    Covers both container-linked and standalone subscriptions.
    """
    session, _, __ = _get_session()
    if not session:
        return

    # Build a map of all local subscriptions with their remote IDs
    # Source 1: Cloud Container records
    containers = frappe.get_all(
        "Cloud Container",
        filters=[["subscription", "is", "set"]],
        fields=["name", "subscription", "remote_subscription"],
    )
    container_by_sub = {c["subscription"]: c for c in containers}

    # Source 2: All local subscriptions (catches ones not linked to a container)
    all_subs = frappe.get_all(
        "Subscription",
        fields=["name", "custom_remote_sub_id"],
    )

    pushed = 0
    pulled = 0

    for sub in all_subs:
        local_sub = sub["name"]
        # Prefer remote ID from the subscription's own field; fall back to container
        container = container_by_sub.get(local_sub)
        remote_sub = sub.get("custom_remote_sub_id") or (container or {}).get("remote_subscription")

        # Backfill custom_remote_sub_id from container if missing
        if remote_sub and not sub.get("custom_remote_sub_id"):
            _store_remote_sub_on_subscription(local_sub, remote_sub)
            frappe.db.commit()

        # Push if not yet synced
        if not remote_sub:
            remote_sub = push_subscription_to_remote(local_sub)
            if remote_sub:
                _store_remote_sub_on_subscription(local_sub, remote_sub)
                if container:
                    frappe.db.set_value("Cloud Container", container["name"], "remote_subscription", remote_sub)
                frappe.db.commit()
                pushed += 1

        # Pull status; if deleted remotely, clear and re-push next cycle
        if remote_sub:
            still_exists = pull_subscription_status(local_sub, remote_sub)
            if not still_exists:
                _store_remote_sub_on_subscription(local_sub, None)
                if container:
                    frappe.db.set_value("Cloud Container", container["name"], "remote_subscription", None)
                frappe.db.commit()
            else:
                pulled += 1

    if pushed or pulled:
        frappe.logger().info(
            f"Subscription sync complete: pushed={pushed}, status_pulls={pulled}"
        )
