"""
=========================================
CV6 AI Trading OS
Main Application
=========================================
"""

# =========================================
# Logging – pre-create log directory
# SmartAPI (Angel One) creates logs/<date>/app.log
# using a relative path; ensure the directory
# exists with an absolute path before any
# broker module is imported.
# =========================================
import os
import time
import pathlib
from loguru import logger

_LOG_DIR = pathlib.Path(__file__).resolve().parent / "logs" / time.strftime("%Y-%m-%d")
_LOG_DIR.mkdir(parents=True, exist_ok=True)

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

# =========================================
# Database
# =========================================

from app.database.session import engine
from app.database.base import Base

# =========================================
# API Routers
# =========================================

from app.api.user_api import router as user_router
from app.api.auth_api import router as auth_router
from app.api.broker_api import router as broker_router
from app.api.market_api import router as market_router
from app.api.protected_api import router as protected_router
from app.api.angel_api import router as angel_router
from app.api.settings_api import router as settings_router
from app.api.alerts_api import router as alerts_router

# =========================================
# AI Router
# =========================================

from app.api.routes.ai import router as ai_router
from app.ai.ai_test import router as ai_test_router

# =========================================
# Indicator Router
# =========================================

from app.indicators.indicator_router import router as indicator_router

# =========================================
# Signal Router
# =========================================

from app.signal.signal_router import router as signal_router

# =========================================
# Strategy Router
# =========================================

from app.strategy.strategy_router import router as strategy_router

# =========================================
# Risk Engine Router (position sizing calc)
# prefix: /risk/engine  — NO conflict with /risk (risk_guard)
# =========================================

from app.risk.risk_router import router as risk_router

# =========================================
# Decision Router
# =========================================

from app.decision.decision_router import router as decision_router

# =========================================
# Portfolio Router
# =========================================

from app.portfolio.portfolio_router import router as portfolio_router

# =========================================
# Paper Trading Router
# =========================================

from app.paper_trading.paper_router import router as paper_router

# =========================================
# Backtest Router
# =========================================

from app.backtest.backtest_router import router as backtest_router

# =========================================
# Register new ORM models so create_all sees them
# =========================================

import app.history.history_model   # noqa: F401 — registers TradeHistory with Base
import app.learning.learning_models # noqa: F401 — registers TradeRecord with Base
import app.execution.idempotency_model  # noqa: F401 — registers IdempotencyRecord with Base (Phase 2.5)
import app.state.state_models        # noqa: F401 — registers TSM tables with Base (Source of Truth)

# =========================================
# Create Database Tables
# =========================================

Base.metadata.create_all(bind=engine)

# =========================================
# Lightweight additive column migrations
# create_all() only creates MISSING TABLES — it never adds a new column to an
# existing table. This idempotently adds columns introduced after a table was
# first created (e.g. tsm_positions.sl_order_id), so an existing DB survives an
# upgrade without a manual migration. SQLite supports ADD COLUMN.
# =========================================
def _ensure_columns():
    from sqlalchemy import inspect, text
    wanted = {
        "tsm_positions": [("sl_order_id", "VARCHAR(80) DEFAULT ''")],
    }
    try:
        insp = inspect(engine)
        existing_tables = set(insp.get_table_names())
        with engine.begin() as conn:
            for table, cols in wanted.items():
                if table not in existing_tables:
                    continue
                have = {c["name"] for c in insp.get_columns(table)}
                for name, ddl in cols:
                    if name not in have:
                        conn.execute(text(f'ALTER TABLE {table} ADD COLUMN {name} {ddl}'))
                        logger.info(f"[Migration] Added column {table}.{name}")
    except Exception as _mig_err:
        logger.warning(f"[Migration] column check skipped: {_mig_err}")

_ensure_columns()

# =========================================
# FastAPI App
# =========================================

app = FastAPI(
    title="CV6 AI Trading OS",
    version="1.0.0",
    description="Complete AI Trading Platform"
)

# =========================================
# CORS — allow frontend on any localhost port
# =========================================
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://localhost:3000",
        "http://localhost:3001",
        "http://localhost:3002",
        "http://127.0.0.1:3000",
        "http://127.0.0.1:3001",
        "http://127.0.0.1:8000",
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# =========================================
# Root API
# =========================================

@app.get("/")
def root():
    return {
        "success": True,
        "message": "CV6 AI Trading OS Running Successfully"
    }

# =========================================
# Include Routers
# =========================================

# Core APIs
app.include_router(user_router)
app.include_router(auth_router)
app.include_router(broker_router)
app.include_router(market_router)
app.include_router(protected_router)
app.include_router(angel_router)
app.include_router(settings_router)
app.include_router(alerts_router)

# AI Engine
app.include_router(ai_router)
app.include_router(ai_test_router)

# Trading Engine
app.include_router(indicator_router)
app.include_router(signal_router)
app.include_router(strategy_router)
app.include_router(risk_router)
app.include_router(decision_router)
app.include_router(portfolio_router)
app.include_router(paper_router)
app.include_router(backtest_router)
from app.execution.execution_router import router as execution_router
app.include_router(execution_router)

# =========================================
# CV6 New Modules — WebSocket, AI Consensus,
# Trade History, Dashboard
# =========================================

# WebSocket — /ws/live  /ws/market  /ws/ticks
from app.websocket.websocket_router import router as websocket_router
app.include_router(websocket_router)

# AI Consensus — /ai/consensus/analyze
from app.ai.consensus_router import router as consensus_router
app.include_router(consensus_router)

# Trade History — /history
from app.history.history_router import router as history_router
app.include_router(history_router)

# Dashboard — /dashboard
from app.dashboard.dashboard_router import router as dashboard_router
app.include_router(dashboard_router)

# Wire dashboard service to AI engine and WebSocket manager
from app.dashboard.dashboard_service import dashboard_service
from app.ai.ai_consensus import consensus_engine
from app.websocket.websocket_manager import manager as ws_manager
from app.websocket.market_stream import stream_engine
from app.history.history_service import history_service as _hist_svc

dashboard_service.set_ai_engine(consensus_engine)
dashboard_service.set_ws_manager(ws_manager)
dashboard_service.set_stream_engine(stream_engine)
dashboard_service.set_history_service(_hist_svc)

# Autonomous AI Trading OS — /autonomous
from app.autonomous.autonomous_router import router as autonomous_router
app.include_router(autonomous_router)

# AI Learning Engine — /learning
from app.learning.learning_router import router as learning_router
app.include_router(learning_router)

# Advanced Risk Guard — /risk
from app.risk_guard.risk_router import router as risk_guard_router
app.include_router(risk_guard_router)

# F&O Engine — /fno
from app.fno.fno_router import router as fno_router
app.include_router(fno_router)

# Trading State — /state  (single source of truth)
from app.state.state_router import router as state_router
app.include_router(state_router)

# =========================================
# Startup: Auto-connect configured brokers
# =========================================

from app.config.settings import settings as _settings

@app.on_event("startup")
async def _auto_connect_brokers():
    """
    Auto-connect Angel One and MStock on startup so the user
    doesn't need to click Connect every morning.
    Only connects if credentials exist in .env.
    """
    import threading

    def _connect_brokers():
        import time
        time.sleep(3)   # let FastAPI finish starting up

        # ── Angel One ──────────────────────────────────────────────
        if _settings.ANGEL_API_KEY and _settings.ANGEL_CLIENT_ID:
            try:
                from app.broker.angel_one import AngelOneBroker
                broker = AngelOneBroker()
                result = broker.connect()
                if result.get("success"):
                    logger.info(
                        f"[Startup] Angel One auto-connected ✅ "
                        f"client={_settings.ANGEL_CLIENT_ID}"
                    )
                    # Share instance with broker_api manager
                    from app.api.broker_api import manager as broker_mgr
                    broker_mgr.broker       = broker
                    broker_mgr.broker_name  = "angelone"
                    broker_mgr.connected    = True

                    # ── Start WebSocket live feed ──────────────────────────
                    try:
                        from app.market.live_feed import live_feed
                        feed_ok = live_feed.connect(broker)
                        if feed_ok:
                            # Subscribe to Nifty 50 index + top 50 stocks
                            _WATCHLIST = [
                                "NIFTY", "BANKNIFTY",
                                "RELIANCE", "TCS", "INFY", "HDFCBANK", "ICICIBANK",
                                "SBIN", "WIPRO", "AXISBANK", "HCLTECH", "KOTAKBANK",
                                "LT", "ITC", "BAJFINANCE", "MARUTI", "SUNPHARMA",
                                "TITAN", "ADANIPORTS", "NTPC", "TATAMOTORS",
                                "TATASTEEL", "HINDUNILVR", "BHARTIARTL", "ONGC",
                                "POWERGRID", "TECHM", "JSWSTEEL", "COALINDIA",
                                "DRREDDY", "CIPLA", "HEROMOTOCO", "EICHERMOT",
                                "HINDALCO", "ULTRACEMCO", "GRASIM", "INDUSINDBK",
                                "BPCL", "DIVISLAB", "NESTLEIND", "BRITANNIA",
                                "APOLLOHOSP", "TATACONSUM", "SBILIFE", "HDFCLIFE",
                                "BAJAJFINSV", "SHRIRAMFIN", "M&M",
                            ]
                            live_feed.subscribe(_WATCHLIST)
                            logger.info(
                                f"[Startup] Live feed started ✅ "
                                f"({len(_WATCHLIST)} symbols subscribed)"
                            )

                            # ── PHASE-2.5 FIX (Goal 3) ──────────────────────
                            # Bridge the real Angel One tick feed into the
                            # browser-facing /ws/live, /ws/market, /ws/ticks
                            # endpoints. Shared with app/broker/broker_manager.py
                            # so a MANUAL /broker/connect also wires this up —
                            # Phase 2 only did it here, on the auto-connect path.
                            from app.websocket.market_stream import connect_angel_one_feed
                            connect_angel_one_feed()
                        else:
                            logger.warning("[Startup] Live feed WebSocket failed to start")
                    except Exception as _feed_err:
                        logger.error(f"[Startup] Live feed error: {_feed_err}")
                else:
                    logger.warning(
                        f"[Startup] Angel One auto-connect failed: "
                        f"{result.get('message')}"
                    )
            except Exception as e:
                logger.error(f"[Startup] Angel One exception: {e}")
        else:
            logger.info("[Startup] Angel One credentials not set — skipping auto-connect")

        # ── MStock ────────────────────────────────────────────────
        if _settings.MSTOCK_API_KEY and _settings.MSTOCK_CLIENT_ID:
            try:
                from app.broker.mstock import MStockBroker
                mstock = MStockBroker()
                result  = mstock.connect()
                if result.get("success"):
                    logger.info(
                        f"[Startup] MStock auto-connected ✅ "
                        f"client={_settings.MSTOCK_CLIENT_ID}"
                    )
                    # Register in broker_mgr so get_broker('mstock') returns
                    # this authenticated instance (not a fresh unauthenticated one)
                    from app.api.broker_api import manager as broker_mgr
                    broker_mgr.register("mstock", mstock)
                else:
                    logger.warning(
                        f"[Startup] MStock auto-connect failed: "
                        f"{result.get('message')}"
                    )
            except Exception as e:
                logger.error(f"[Startup] MStock exception: {e}")
        else:
            logger.info("[Startup] MStock credentials not set — skipping auto-connect")

        # ── SOURCE OF TRUTH: recover the TradingStateManager ────────────────
        # 1) rebuild persisted state from DB (orders/positions/capital survive
        #    restart), 2) reconcile against the broker's real position book so
        #    any position opened before the restart is restored authoritatively.
        try:
            from app.state.trading_state_manager import trading_state_manager
            from app.api.broker_api import manager as _bmgr
            trading_state_manager.bootstrap()

            broker_positions = []
            seen = set()
            candidates = []
            if _bmgr.connected and _bmgr.broker is not None:
                candidates.append((_bmgr.broker_name or "", _bmgr.broker))
            for _n, _inst in getattr(_bmgr, "_extra_brokers", {}).items():
                if _inst is not None and id(_inst) not in seen:
                    candidates.append((_n, _inst)); seen.add(id(_inst))
            for _bname, _binst in candidates:
                try:
                    raw = _binst.positions()
                except Exception:
                    continue
                plist = raw.get("data") if isinstance(raw, dict) else (raw if isinstance(raw, list) else [])
                for p in (plist or []):
                    q = p.get("netqty", p.get("quantity", 0))
                    try:
                        q = int(float(q))
                    except (TypeError, ValueError):
                        q = 0
                    if q == 0:
                        continue
                    broker_positions.append({
                        "symbol":   str(p.get("tradingsymbol") or p.get("symbol", "")),
                        "exchange": str(p.get("exchange", "NSE")),
                        "broker":   _bname,
                        "netqty":   q,
                        "avg_price": float(p.get("averageprice") or p.get("average_price") or 0),
                        "ltp":      float(p.get("ltp") or p.get("lastprice") or 0),
                    })
            recon_tsm = trading_state_manager.reconcile_with_broker(broker_positions)
            logger.info(f"[Startup] TSM reconciliation: {recon_tsm}")
        except Exception as _tsm_err:
            logger.error(f"[Startup] TSM recovery failed: {_tsm_err}")

        # ── PHASE-2.5 FIX (Goal 4) ──────────────────────────────────────────
        # Also reconcile the autonomous engine's internal working pools (the
        # monitor loop's scratch state), from the same broker book.
        try:
            from app.autonomous.autonomous_engine import autonomous_engine
            recon = autonomous_engine.bootstrap_pools_and_reconcile()
            logger.info(f"[Startup] Engine pool reconciliation: {recon}")
        except Exception as _recon_err:
            logger.error(f"[Startup] Engine pool reconciliation failed: {_recon_err}")

    threading.Thread(target=_connect_brokers, daemon=True).start()


@app.get("/market/feed/status")
def live_feed_status():
    """
    Live WebSocket feed status — connected, subscribed symbols, cached ticks.
    source = 'websocket' when live feed is running.
    """
    try:
        from app.market.live_feed import live_feed
        return {"success": True, **live_feed.status()}
    except Exception as e:
        return {"success": False, "connected": False, "error": str(e)}


@app.post("/market/feed/subscribe")
def feed_subscribe(symbols: list):
    """Dynamically add symbols to the live WebSocket feed."""
    try:
        from app.market.live_feed import live_feed
        live_feed.subscribe(symbols)
        return {"success": True, "subscribed": symbols}
    except Exception as e:
        return {"success": False, "error": str(e)}


@app.get("/broker/status")
def broker_status():
    """
    Quick status check for all configured brokers.
    Shows connected/disconnected state without needing to open Brokers page.
    """
    from app.api.broker_api import manager as broker_mgr

    angel_creds  = bool(_settings.ANGEL_API_KEY and _settings.ANGEL_CLIENT_ID)
    mstock_creds = bool(_settings.MSTOCK_API_KEY and _settings.MSTOCK_CLIENT_ID)

    # Check secondary registry for MStock connection status
    mstock_instance = broker_mgr._extra_brokers.get("mstock")
    mstock_connected = bool(mstock_instance and mstock_instance.is_connected())

    return {
        "angelone": {
            "credentials_set": angel_creds,
            "connected": broker_mgr.connected and broker_mgr.broker_name == "angelone",
            "client_id": _settings.ANGEL_CLIENT_ID if angel_creds else None,
        },
        "mstock": {
            "credentials_set": mstock_creds,
            "connected": mstock_connected,
            "client_id": _settings.MSTOCK_CLIENT_ID if mstock_creds else None,
        },
        "active_broker": broker_mgr.broker_name,
        "active_connected": broker_mgr.connected,
    }