import frappe
import requests
from frappe.utils import now_datetime

BASE_URL = "https://my.birbank.business/api/b2b"
LOGIN_URL = f"{BASE_URL}/login"
REFRESH_URL = f"{BASE_URL}/refresh"

BROWSER_HEADERS = {
    "Content-Type": "application/json",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Accept": "application/json, text/plain, */*",
    "Accept-Language": "en-US,en;q=0.9",
    "Origin": "https://my.birbank.business",
    "Referer": "https://my.birbank.business/",
}


def _get_doc(login_name):
    return frappe.get_doc("Kapital Bank Login", login_name)


@frappe.whitelist()
def login(login_name):
    """POST /api/b2b/login — get JWT token and refresh token."""
    doc = _get_doc(login_name)
    password = doc.get_password("password")

    payload = {
        "username": doc.username,
        "password": password
    }

    try:
        resp = requests.post(LOGIN_URL, json=payload, headers=BROWSER_HEADERS, timeout=15)
    except requests.RequestException as e:
        frappe.log_error(f"Network error during login for {login_name}: {e}", "KB Login Error")
        return {"success": False, "message": f"Network error: {e}"}

    if resp.status_code == 200:
        try:
            data = resp.json()
        except Exception:
            frappe.log_error(f"Invalid JSON response: {resp.text[:500]}", "KB Login Error")
            return {"success": False, "message": "Invalid response from server"}

        resp_data = data.get("responseData", {})
        code = data.get("response", {}).get("code", "")

        if code != "0":
            msg = data.get("response", {}).get("message", "Login failed")
            frappe.log_error(f"Login failed for {login_name}: code={code} msg={msg}", "KB Login Error")
            frappe.db.set_value("Kapital Bank Login", login_name, "auth_status", "Failed", update_modified=False)
            frappe.db.commit()
            return {"success": False, "message": msg}

        jwt_token = resp_data.get("jwttoken", "")
        jwt_refresh = resp_data.get("jwtrefreshtoken", "")

        user_info = resp_data.get("userInfo", {})
        name_val = f"{user_info.get('name', '')} {user_info.get('surname', '')}".strip()
        chat_data = user_info.get("chatData", {}).get("clientData", {})
        company = chat_data.get("company", "")
        cif = chat_data.get("cif", "")

        frappe.db.set_value("Kapital Bank Login", login_name, {
            "jwt_token": jwt_token,
            "jwt_refresh_token": jwt_refresh,
            "auth_status": "Authenticated",
            "last_activity": now_datetime(),
            "user_info": name_val,
            "company_name": company,
            "cif": cif,
        }, update_modified=False)
        frappe.db.commit()

        return {"success": True, "message": "Login successful"}

    elif resp.status_code == 401:
        frappe.db.set_value("Kapital Bank Login", login_name, "auth_status", "Failed", update_modified=False)
        frappe.db.commit()
        return {"success": False, "message": "Invalid credentials (401)"}

    else:
        frappe.log_error(
            f"Login failed for {login_name}: HTTP {resp.status_code} — {resp.text[:300]}",
            "KB Login Error"
        )
        frappe.db.set_value("Kapital Bank Login", login_name, "auth_status", "Failed", update_modified=False)
        frappe.db.commit()
        return {"success": False, "message": f"Server error: HTTP {resp.status_code}"}


@frappe.whitelist()
def logout(login_name):
    """Clear tokens and mark as Not Authenticated."""
    frappe.db.set_value("Kapital Bank Login", login_name, {
        "jwt_token": "",
        "jwt_refresh_token": "",
        "auth_status": "Not Authenticated",
        "user_info": "",
        "company_name": "",
        "cif": "",
    }, update_modified=False)
    frappe.db.commit()
    return {"success": True, "message": "Logged out"}


def _relogin(login_name):
    """Auto re-login using stored credentials. Returns new jwt_token or raises."""
    frappe.logger().info(f"[KB] Attempting auto re-login for {login_name}")
    result = login(login_name)
    if result.get("success"):
        doc = _get_doc(login_name)
        new_token = doc.get_password("jwt_token") if doc.jwt_token else ""
        if new_token:
            frappe.logger().info(f"[KB] Auto re-login successful for {login_name}")
            return new_token
    frappe.log_error(f"Auto re-login failed for {login_name}: {result.get('message')}", "KB Relogin Error")
    raise frappe.AuthenticationError(f"Auto re-login failed: {result.get('message')}")


def refresh_token(login_name):
    """POST /api/b2b/refresh — get new JWT using refresh token.
    Falls back to full re-login if refresh token is expired/invalid."""
    doc = _get_doc(login_name)
    refresh_tok = doc.get_password("jwt_refresh_token") if doc.jwt_refresh_token else ""

    if not refresh_tok:
        frappe.logger().info(f"[KB] No refresh token for {login_name}, attempting re-login")
        return _relogin(login_name)

    payload = {"refreshToken": refresh_tok}

    try:
        resp = requests.post(REFRESH_URL, json=payload, headers=BROWSER_HEADERS, timeout=15)
    except requests.RequestException as e:
        frappe.log_error(f"Network error during refresh for {login_name}: {e}", "KB Refresh Error")
        raise

    if resp.status_code == 200:
        try:
            data = resp.json()
        except Exception:
            frappe.log_error(f"Invalid JSON in refresh response: {resp.text[:300]}", "KB Refresh Error")
            raise frappe.ValidationError("Invalid refresh response")

        resp_code = data.get("response", {}).get("code", "")
        if resp_code == "100":
            frappe.logger().info(f"[KB] Refresh token invalid (code 100) for {login_name}, attempting re-login")
            return _relogin(login_name)

        resp_data = data.get("responseData", {})
        new_jwt = resp_data.get("jwttoken", "")
        new_refresh = resp_data.get("jwtrefreshtoken", "")

        frappe.db.set_value("Kapital Bank Login", login_name, {
            "jwt_token": new_jwt,
            "jwt_refresh_token": new_refresh,
            "auth_status": "Authenticated",
            "last_activity": now_datetime(),
        }, update_modified=False)
        frappe.db.commit()

        return new_jwt

    elif resp.status_code == 401:
        frappe.logger().info(f"[KB] Refresh token expired (401) for {login_name}, attempting re-login")
        return _relogin(login_name)

    else:
        frappe.log_error(
            f"Refresh failed for {login_name}: HTTP {resp.status_code} — {resp.text[:300]}",
            "KB Refresh Error"
        )
        raise frappe.ValidationError(f"Refresh failed: HTTP {resp.status_code}")


def get_default_login():
    """Return the default Kapital Bank Login document name, or None."""
    rows = frappe.get_all("Kapital Bank Login", filters={"is_default": 1}, fields=["name"], limit=1)
    if not rows:
        rows = frappe.get_all("Kapital Bank Login", filters={"auth_status": "Authenticated"}, fields=["name"], limit=1)
    return rows[0].name if rows else None


@frappe.whitelist()
def renew_token(login_name=None):
    """Cron entry — refresh token for the default (or specified) login."""
    try:
        if not login_name:
            login_name = get_default_login()
        if not login_name:
            return {"success": False, "message": "No default Kapital Bank Login found"}

        doc = _get_doc(login_name)
        if doc.auth_status == "Authenticated":
            new_token = refresh_token(login_name)
            frappe.logger().info(f"[KB] Token renewed for {login_name}")
            return {"success": True, "message": "Token renewed"}

        # Not authenticated — try full re-login with stored credentials
        frappe.logger().info(f"[KB] Login {login_name} is {doc.auth_status}, attempting re-login")
        new_token = _relogin(login_name)
        return {"success": True, "message": "Re-login successful"}

    except Exception as e:
        frappe.log_error(f"renew_token failed: {e}\n{frappe.get_traceback()}", "KB Token Renewal")
        return {"success": False, "message": str(e)}
