import logging
import time
from datetime import datetime, timedelta
from typing import Tuple, List, Dict

import requests
from bs4 import BeautifulSoup
from openai import OpenAI
from dotenv import load_dotenv

from .strategy import BearishDivergenceStrategy  # your existing strategy

load_dotenv()
client = OpenAI()  # reads OPENAI_API_KEY from env
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class TopMoversFetcher:
    def __init__(self, db):
        self.db = db

    def fetch_top_movers(self) -> Tuple[List[Dict[str, str]], List[Dict[str, str]]]:
        """Fetch top gainers and losers (with caching & mapping)."""
        try:
            # 1) Check cache
            cached = self.db["et_movers"].find_one({"type": "top_movers"})
            if cached and (datetime.now() - cached["last_updated"]) < timedelta(minutes=30):
                return cached["gainers"], cached["losers"]

            # 2) Scrape both pages
            raw_gainers = self._scrape_list("top-gainers")
            raw_losers  = self._scrape_list("top-losers")

            # 3) Map to NSE symbols
            strategy   = BearishDivergenceStrategy(
                api_key      = "dummy",
                api_secret   = "dummy",
                access_token = "dummy",
                db           = self.db,
            )
            symbol_map = strategy.map_et_to_zerodha(raw_gainers + raw_losers)

            # 4) Build the payloads
            gainers = [
                {"raw_name": raw, "nse_symbol": symbol_map.get(raw), "mapped_at": datetime.now()}
                for raw in raw_gainers
            ]
            losers = [
                {"raw_name": raw, "nse_symbol": symbol_map.get(raw), "mapped_at": datetime.now()}
                for raw in raw_losers
            ]

            # 5) Cache
            self.db["et_movers"].update_one(
                {"type": "top_movers"},
                {"$set": {"gainers": gainers, "losers": losers, "last_updated": datetime.now()}},
                upsert=True
            )
            return gainers, losers

        except Exception as e:
            logger.error(f"Error in fetch_top_movers: {e}", exc_info=True)
            return [], []

    def _scrape_list(self, slug: str) -> List[str]:
        """
        Fetch the given ET page (gainers or losers) and extract the raw names.
        slug should be "top-gainers" or "top-losers".
        """
        url = f"https://economictimes.indiatimes.com/stocks/marketstats/{slug}"
        headers = {
            "User-Agent": (
                "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
                "AppleWebKit/537.36 (KHTML, like Gecko) "
                "Chrome/122.0.0.0 Safari/537.36"
            )
        }

        # Primary path: lightweight HTTP fetch + BeautifulSoup.
        try:
            resp = requests.get(url, headers=headers, timeout=10)
            resp.raise_for_status()

            soup = BeautifulSoup(resp.text, "html.parser")
            rows = soup.select("#table tbody tr")
            symbols: List[str] = []
            for row in rows:
                a = row.select_one("a.MarketTable_ellipses__M8PxM")
                if a and a.get("href"):
                    try:
                        symbols.append(self._extract_symbol_from_href(a["href"]))
                    except Exception:
                        continue

            if symbols:
                return symbols

            logger.warning(
                "TopMoversFetcher._scrape_list: HTTP scrape returned no symbols; "
                "falling back to headless browser."
            )
        except Exception as e:
            logger.warning(
                "TopMoversFetcher._scrape_list: HTTP fetch failed (%s); "
                "falling back to headless browser.",
                e,
            )

        # Fallback path: try a headless browser scrape if Selenium is available.
        return self._scrape_list_via_selenium(slug)


    def _scrape_movers(self) -> Tuple[List[str], List[str]]:
        """
        Legacy helper to load the ET gainers page and grab gainers.

        Currently this is implemented via the same HTTP + BeautifulSoup
        approach as `_scrape_list`, and returns (raw_gainers, losers=[]).
        """
        raw_gainers = self._scrape_list("top-gainers")
        losers: List[str] = []
        return raw_gainers, losers

    def _scrape_list_via_selenium(self, slug: str) -> List[str]:
        """Best-effort fallback using a headless Chrome browser.

        This is only used if the primary HTTP+BeautifulSoup path fails
        or returns no symbols. If Selenium or a compatible driver is not
        installed on the server, this will log an error and return an
        empty list rather than crashing the app.
        """
        try:
            from selenium import webdriver
            from selenium.webdriver.chrome.options import Options
        except ImportError:
            logger.error(
                "TopMoversFetcher._scrape_list_via_selenium: Selenium not installed; "
                "cannot perform browser-based fallback scrape."
            )
            return []

        url = f"https://economictimes.indiatimes.com/stocks/marketstats/{slug}"
        opts = Options()
        # Use modern headless mode where available; falls back if unsupported.
        try:
            opts.add_argument("--headless=new")
        except Exception:
            opts.add_argument("--headless")
        opts.add_argument("--disable-gpu")
        opts.add_argument("--no-sandbox")

        driver = webdriver.Chrome(options=opts)
        try:
            driver.get(url)
            # Allow some time for any JS-rendered content, though ET pages are
            # mostly static; keep this conservative to avoid overloading.
            time.sleep(5)

            soup = BeautifulSoup(driver.page_source, "html.parser")
            rows = soup.select("#table tbody tr")
            symbols: List[str] = []
            for row in rows:
                a = row.select_one("a.MarketTable_ellipses__M8PxM")
                if a and a.get("href"):
                    try:
                        symbols.append(self._extract_symbol_from_href(a["href"]))
                    except Exception:
                        continue
            return symbols
        except Exception as e:
            logger.error(
                "TopMoversFetcher._scrape_list_via_selenium: headless scrape failed: %s",
                e,
                exc_info=True,
            )
            return []
        finally:
            try:
                driver.quit()
            except Exception:
                pass

    @staticmethod
    def _extract_symbol_from_href(href: str) -> str:
        """
        '/glenmark-pharmaceuticals-ltd/stocks/companyid-4255.cms' →
        'GLENMARK_PHARMACEUTICALS_LTD'
        """
        parts = href.split("/stocks")[0]
        return parts.split("/")[-1].upper().replace("-", "_")


def map_company_to_symbol(companies: List[str]) -> List[str]:
    """
    Use OpenAI to convert company names to NSE trading symbols
    (Zerodha KiteConnect style), returning a raw list of uppercase strings.
    """
    prompt = (
        "You are a financial assistant. Convert the following Indian company names to their "
        "correct NSE trading symbols (used by Zerodha KiteConnect). "
        "Return ONLY a valid Python list of UPPERCASE strings.\n\n"
        f"Company Names: {companies}"
    )
    try:
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": prompt}],
            temperature=0.2
        )
        content = response.choices[0].message.content.strip()
        if content.startswith("["):
            return eval(content)
        # Keep this at DEBUG to avoid noisy terminal logs.
        # (We already return [] here, so the caller can decide what to do.)
        logger.debug("Unexpected format from OpenAI: %s", content)
        return []
    except Exception as e:
        logger.error("OpenAI API call failed: %s", e)
        return []


def get_top_gainer_symbols(db) -> List[str]:
    """Helper to return just the valid NSE symbols from today’s gainers."""
    logger.info("Fetching top gainers…")
    fetcher = TopMoversFetcher(db)
    gainers, _ = fetcher.fetch_top_movers()
    symbols: List[str] = []
    for g in gainers:
        # Prefer mapped NSE symbol; if missing, fall back to raw_name so
        # we never drop the true top gainer row at index 0.
        sym = (g.get("nse_symbol") or g.get("raw_name") or "").strip().upper()
        if sym:
            symbols.append(sym)
    logger.info("Found %d gainer symbols (including raw fallbacks): %s", len(symbols), symbols)
    return symbols

def get_top_loser_symbols(db) -> List[str]:
    logger.info("Fetching top losers…")
    fetcher = TopMoversFetcher(db)
    _, losers = fetcher.fetch_top_movers()
    symbols: List[str] = []
    for l in losers:
        sym = (l.get("nse_symbol") or l.get("raw_name") or "").strip().upper()
        if sym:
            symbols.append(sym)
    logger.info("Found %d loser symbols (including raw fallbacks): %s", len(symbols), symbols)
    return symbols


def main():
    """
    Standalone debug entrypoint — requires a `db` argument
    or you can stub one in if you just want to test scraping.
    """
    # Example stub: from pymongo import MongoClient; db = MongoClient().test
    from pymongo import MongoClient
    db = MongoClient().test

    print("=== ET Top Movers Fetcher ===")
    fetcher = TopMoversFetcher(db)
    start = time.time()
    gainers, losers = fetcher.fetch_top_movers()
    print(f"Fetched in {time.time()-start:.2f}s → {len(gainers)} gainers, {len(losers)} losers")
    print("Gainers:", gainers)


if __name__ == "__main__":
    main()
