"""
=========================================
CV6 AI Trading OS — MStock Broker
Mirae Asset Securities India
=========================================
Uses MStock TypeA REST API.

Official docs: https://tradingapi.mstock.com/docs/v1/typeA/
Base URL:      https://api.mstock.trade

Auth (TOTP flow — no username/password needed if TOTP enabled):
  POST /openapi/typea/session/verifytotp
    form: api_key=<key>&totp=<6-digit TOTP>
  → returns access_token

All subsequent calls use:
  Authorization: token <api_key>:<access_token>
  X-Mirae-Version: 1

Credentials loaded from .env:
  MSTOCK_API_KEY   — API key (base64 format)
  MSTOCK_SECRET    — TOTP secret (base32, used with pyotp)
  MSTOCK_CLIENT_ID — Client ID e.g. MA58886
=========================================
"""
from __future__ import annotations

import time
from typing import Any, Dict, Optional

import pyotp
import requests
from loguru import logger

from app.broker.base_broker import BaseBroker
from app.config.settings import settings

_BASE     = "https://api.mstock.trade"
_PREFIX   = "/openapi/typea"
_TIMEOUT  = 15


class MStockBroker(BaseBroker):
    """
    MStock (Mirae Asset India) broker — TypeA API implementation.
    """

    def __init__(self):
        self.api_key     = settings.MSTOCK_API_KEY
        self.totp_secret = settings.MSTOCK_SECRET      # base32 TOTP seed
        self.client_id   = settings.MSTOCK_CLIENT_ID
        self._access_token: str = ""
        self._connected: bool   = False
        self._profile_data: dict = {}
        self._session = requests.Session()

    # ── Internal helpers ───────────────────────────────────────────

    def _totp(self) -> str:
        """Generate current 6-digit TOTP from the stored secret."""
        try:
            return pyotp.TOTP(self.totp_secret).now()
        except Exception as e:
            logger.error(f"[MStock] TOTP generation failed: {e}")
            return ""

    def _auth_header(self) -> str:
        """MStock auth header format: token api_key:access_token"""
        return f"token {self.api_key}:{self._access_token}"

    def _headers(self, content_type: str = "application/x-www-form-urlencoded") -> Dict[str, str]:
        return {
            "X-Mirae-Version": "1",
            "Authorization":   self._auth_header(),
            "Content-Type":    content_type,
        }

    def _headers_noauth(self) -> Dict[str, str]:
        return {
            "X-Mirae-Version": "1",
            "Content-Type":    "application/x-www-form-urlencoded",
        }

    # ── TIER-2 (2A): session-expiry recovery ────────────────────────────────
    def _relogin(self) -> bool:
        """Force a fresh MStock session (used on detected expiry)."""
        self._connected = False
        self._access_token = ""
        try:
            res = self.connect()
            return bool(res.get("success"))
        except Exception as e:
            logger.error(f"[MStock] Re-login exception: {e}")
            return False

    def validate_session(self) -> bool:
        """Proactive check called periodically by the engine."""
        if not self._connected or not self._access_token:
            return self._relogin()
        return True

    def _post_form(self, path: str, data: dict, auth: bool = True, _retried: bool = False) -> dict:
        url = f"{_BASE}{_PREFIX}{path}"
        headers = self._headers() if auth else self._headers_noauth()
        try:
            r = self._session.post(url, data=data, headers=headers, timeout=_TIMEOUT)
            logger.debug(f"[MStock] POST {path} → {r.status_code}")
            r.raise_for_status()
            return r.json()
        except requests.HTTPError as e:
            code = getattr(e.response, "status_code", 0)
            body = ""
            try:
                body = e.response.text[:300]
            except Exception:
                pass
            logger.error(f"[MStock] HTTP {code} on POST {path}: {body}")
            if code in (401, 403) and auth and not _retried:
                logger.warning(f"[MStock] Session expired on POST {path} — re-login")
                if self._relogin():
                    return self._post_form(path, data, auth=auth, _retried=True)
            return {"status": "error", "message": f"HTTP {code}: {body}"}
        except Exception as e:
            logger.error(f"[MStock] POST {path} error: {e}")
            return {"status": "error", "message": str(e)}

    def _get(self, path: str, data: Optional[dict] = None, _retried: bool = False) -> dict:
        url = f"{_BASE}{_PREFIX}{path}"
        headers = self._headers()
        try:
            r = self._session.get(url, data=data, headers=headers, timeout=_TIMEOUT)
            logger.debug(f"[MStock] GET {path} → {r.status_code}")
            r.raise_for_status()
            return r.json()
        except requests.HTTPError as e:
            code = getattr(e.response, "status_code", 0)
            body = ""
            try:
                body = e.response.text[:300]
            except Exception:
                pass
            logger.error(f"[MStock] HTTP {code} on GET {path}: {body}")
            if code in (401, 403) and not _retried:
                logger.warning(f"[MStock] Session expired on GET {path} — re-login")
                if self._relogin():
                    return self._get(path, data, _retried=True)
            return {"status": "error", "message": f"HTTP {code}: {body}"}
        except Exception as e:
            logger.error(f"[MStock] GET {path} error: {e}")
            return {"status": "error", "message": str(e)}

    def _put_form(self, path: str, data: dict) -> dict:
        url = f"{_BASE}{_PREFIX}{path}"
        headers = self._headers()
        try:
            r = self._session.put(url, data=data, headers=headers, timeout=_TIMEOUT)
            logger.debug(f"[MStock] PUT {path} → {r.status_code}")
            r.raise_for_status()
            return r.json()
        except requests.HTTPError as e:
            body = ""
            try:
                body = e.response.text[:300]
            except Exception:
                pass
            logger.error(f"[MStock] HTTP {e.response.status_code} on PUT {path}: {body}")
            return {"status": "error", "message": f"HTTP {e.response.status_code}: {body}"}
        except Exception as e:
            logger.error(f"[MStock] PUT {path} error: {e}")
            return {"status": "error", "message": str(e)}

    def _delete(self, path: str) -> dict:
        url = f"{_BASE}{_PREFIX}{path}"
        headers = {
            "X-Mirae-Version": "1",
            "Authorization":   self._auth_header(),
        }
        try:
            r = self._session.delete(url, headers=headers, timeout=_TIMEOUT)
            logger.debug(f"[MStock] DELETE {path} → {r.status_code}")
            r.raise_for_status()
            return r.json()
        except requests.HTTPError as e:
            body = ""
            try:
                body = e.response.text[:300]
            except Exception:
                pass
            logger.error(f"[MStock] HTTP {e.response.status_code} on DELETE {path}: {body}")
            return {"status": "error", "message": f"HTTP {e.response.status_code}: {body}"}
        except Exception as e:
            logger.error(f"[MStock] DELETE {path} error: {e}")
            return {"status": "error", "message": str(e)}

    # ── Connection ─────────────────────────────────────────────────

    def connect(self) -> dict:
        if self._connected and self._access_token:
            return {"success": True, "message": "Already connected", "broker": "mstock"}

        if not self.api_key or not self.totp_secret:
            return {
                "success": False,
                "message": "MStock credentials missing in .env (MSTOCK_API_KEY, MSTOCK_SECRET)"
            }

        try:
            totp_code = self._totp()
            if not totp_code:
                return {"success": False, "message": "MStock: failed to generate TOTP"}

            logger.info(f"[MStock] Authenticating with TOTP for client {self.client_id}")

            res = self._post_form(
                "/session/verifytotp",
                {"api_key": self.api_key, "totp": totp_code},
                auth=False,
            )

            if res.get("status") != "success":
                msg = res.get("message") or "TOTP authentication failed"
                logger.warning(f"[MStock] Login failed: {msg}")
                return {"success": False, "message": f"MStock: {msg}"}

            data = res.get("data") or {}
            token = (
                data.get("access_token")
                or data.get("accessToken")
                or ""
            )

            if not token:
                return {
                    "success": False,
                    "message": "MStock: TOTP succeeded but no access_token in response"
                }

            self._access_token = token
            self._connected     = True
            self._profile_data  = data

            logger.info(f"[MStock] Connected — client {self.client_id}, user: {data.get('user_name', '')}")
            return {
                "success":   True,
                "broker":    "mstock",
                "client_id": self.client_id,
                "user_name": data.get("user_name", ""),
                "message":   "MStock connected successfully",
            }

        except Exception as e:
            self._connected = False
            logger.error(f"[MStock] connect() exception: {e}")
            return {"success": False, "message": str(e)}

    def disconnect(self) -> dict:
        try:
            if self._access_token:
                self._get("/logout")
        except Exception:
            pass
        self._connected     = False
        self._access_token  = ""
        self._profile_data  = {}
        logger.info("[MStock] Disconnected")
        return {"success": True}

    def is_connected(self) -> bool:
        return self._connected

    # ── Account ────────────────────────────────────────────────────

    def profile(self) -> dict:
        if not self._connected:
            return {"success": False, "message": "Not connected"}
        # Profile data is cached from login response
        if self._profile_data:
            return {"success": True, "data": self._profile_data}
        return {"success": True, "data": {"client_id": self.client_id}}

    def funds(self) -> dict:
        if not self._connected:
            return {"success": False, "message": "Not connected"}
        res = self._get("/user/fundsummary")
        if res.get("status") == "success":
            raw_list = res.get("data") or []
            # Find equity segment (SEG = "A" for equity)
            equity = next((x for x in raw_list if x.get("SEG") == "A"), raw_list[0] if raw_list else {})
            return {
                "success":        True,
                "available_cash": float(equity.get("AVAILABLE_BALANCE") or 0),
                "used_margin":    float(equity.get("AMOUNT_UTILIZED") or 0),
                "raw":            res,
            }
        return {"success": False, "message": res.get("message", "Funds fetch failed")}

    def holdings(self) -> dict:
        if not self._connected:
            return {"success": False, "message": "Not connected"}
        return self._get("/portfolio/holdings")

    def positions(self) -> dict:
        if not self._connected:
            return {"success": False, "message": "Not connected"}
        return self._get("/portfolio/positions")

    def orders(self) -> dict:
        if not self._connected:
            return {"success": False, "message": "Not connected"}
        return self._get("/orders")

    def tradebook(self) -> dict:
        if not self._connected:
            return {"success": False, "message": "Not connected"}
        return self._get("/tradebook")

    # ── Order management ───────────────────────────────────────────

    def place_order(self, order: Dict[str, Any]) -> dict:
        if not self._connected:
            return {"success": False, "message": "Not connected"}

        # Map CV6 fields → MStock TypeA API fields
        # product mapping: INTRADAY→MIS, DELIVERY/CNC→CNC, CARRYFORWARD/NRML→NRML
        raw_product = order.get("producttype") or order.get("product", "MIS")
        product_map = {
            "INTRADAY": "MIS",
            "DELIVERY": "CNC",
            "CNC":      "CNC",
            "CARRYFORWARD": "NRML",
            "NRML":     "NRML",
            "MIS":      "MIS",
            "MTF":      "MTF",
        }
        product = product_map.get(str(raw_product).upper(), "MIS")

        # variety in URL path: regular, amo, co
        variety_raw = str(order.get("variety", "NORMAL")).upper()
        if variety_raw == "AMO":
            variety = "amo"
        elif variety_raw in ("CO", "COVER"):
            variety = "co"
        else:
            variety = "regular"

        payload = {
            "tradingsymbol":    order.get("tradingsymbol") or order.get("symbol", ""),
            "exchange":         order.get("exchange", "NSE"),
            "transaction_type": order.get("transactiontype") or order.get("transaction_type", "BUY"),
            "order_type":       order.get("ordertype") or order.get("order_type", "MARKET"),
            "quantity":         str(int(order.get("quantity", 0))),
            "product":          product,
            "validity":         order.get("duration") or order.get("validity", "DAY"),
            "price":            str(float(order.get("price", 0))),
        }

        trigger = float(order.get("triggerprice") or order.get("trigger_price") or 0)
        if trigger > 0:
            payload["trigger_price"] = str(trigger)

        res = self._post_form(f"/orders/{variety}", payload)

        if res.get("status") == "success":
            data = res.get("data") or {}
            order_id = data.get("order_id") or data.get("orderId") or ""
            logger.info(
                f"[MStock] Order placed: {order_id} | "
                f"{payload['tradingsymbol']} {payload['transaction_type']} "
                f"{payload['quantity']} @ {payload['price']}"
            )
            return {"success": True, "order_id": order_id, "raw": res}
        else:
            msg = res.get("message") or "Order placement failed"
            logger.error(f"[MStock] Place order failed: {msg}")
            return {"success": False, "message": msg}

    def modify_order(self, order_id: str, order: Dict[str, Any]) -> dict:
        if not self._connected:
            return {"success": False, "message": "Not connected"}

        payload = {}
        if order.get("ordertype"):
            payload["order_type"] = order["ordertype"]
        if order.get("quantity"):
            payload["quantity"] = str(order["quantity"])
        if order.get("price"):
            payload["price"] = str(order["price"])
        if order.get("triggerprice"):
            payload["trigger_price"] = str(order["triggerprice"])
        payload.setdefault("validity", "DAY")
        payload.setdefault("disclosed_quantity", "0")

        res = self._put_form(f"/orders/regular/{order_id}", payload)
        if res.get("status") == "success":
            return {"success": True, "order_id": order_id, "raw": res}
        return {"success": False, "message": res.get("message", "Modify failed")}

    def cancel_order(self, order_id: str) -> dict:
        if not self._connected:
            return {"success": False, "message": "Not connected"}

        res = self._delete(f"/orders/regular/{order_id}")
        if res.get("status") == "success":
            return {"success": True, "order_id": order_id, "raw": res}
        return {"success": False, "message": res.get("message", "Cancel failed")}

    # ── Market data ────────────────────────────────────────────────

    def quotes(self, symbol: Dict[str, Any]) -> dict:
        # MStock quotes via market-quote endpoint
        if not self._connected:
            return {"success": False, "message": "Not connected"}
        exchange = symbol.get("exchange", "NSE")
        sym      = symbol.get("tradingsymbol", "")
        return self._get(f"/quote/ohlc?exchange={exchange}&tradingsymbol={sym}")

    def historical_data(self, params: Dict[str, Any]) -> dict:
        if not self._connected:
            return {"success": False, "message": "Not connected"}
        return self._post_form("/historical/candle", params)

    def live_data(self, symbol: Dict[str, Any]) -> dict:
        return self.quotes(symbol)
