import argparse
import os
from datetime import datetime
from pathlib import Path
from typing import List, Tuple

import pandas as pd
import futu as ft

from rqalpha.const import EXCHANGE
from .constants import FUTU_HOST, FUTU_PORT
from .utils import rq_to_futu_code, get_market_dir


PERIOD_MAP = {
    "1d": ft.KLType.K_DAY,
    "1m": ft.KLType.K_1M,
    "3m": ft.KLType.K_3M,
    "5m": ft.KLType.K_5M,
    "1w": ft.KLType.K_WEEK,
    "1mo": ft.KLType.K_MON,
}


def validate_time(time_str: str | None):
    if not time_str:
        return
    for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d"):
        try:
            datetime.strptime(time_str, fmt)
            return
        except ValueError:
            continue
    raise ValueError(f"Invalid time format or value: {time_str}")


def parse_codes(raw: List[str]) -> List[Tuple[str, str, str]]:
    """
    Returns list of (market, symbol, order_book_id)
    """
    res: List[Tuple[str, str, str]] = []
    for c in raw:
        c = c.strip()
        if not c:
            continue
        parts = c.split(".")
        if len(parts) == 2 and parts[1].upper() in (
            EXCHANGE.XSHG,
            EXCHANGE.XSHE,
            EXCHANGE.XHKG,
            EXCHANGE.XNAS,
            EXCHANGE.XNYS,
        ):
            market, symbol = rq_to_futu_code(c)
            res.append((market, symbol, c.upper()))
        else:
            raise ValueError(
                f"Invalid code format: {c}. Please use RQAlpha format (e.g., 000001.XSHE, AAPL.XNAS)."
            )
    return res


def get_store_dir(root: str, order_book_id: str) -> Path:
    market_dir = get_market_dir(order_book_id)
    return Path(root, market_dir, order_book_id)


def ensure_dir(root: str, order_book_id: str):
    get_store_dir(root, order_book_id).mkdir(parents=True, exist_ok=True)


def file_name(period: str) -> str:
    return f"{period}.csv"


def save_csv(root: str, order_book_id: str, period: str, df: pd.DataFrame):
    cols = ["time_key", "open", "high", "low", "close", "volume", "turnover"]
    out = df[cols].copy()
    ensure_dir(root, order_book_id)
    store_dir = get_store_dir(root, order_book_id)
    out.to_csv(store_dir / file_name(period), index=False)


def fetch_history(
    ctx: ft.OpenQuoteContext, code: str, period: str, start: str | None, end: str | None
) -> pd.DataFrame:
    ktype = PERIOD_MAP[period]
    ret, data, page_req_key = ctx.request_history_kline(
        code,
        start=start,
        end=end,
        ktype=ktype,
        autype=ft.AuType.QFQ,
        fields=[ft.KL_FIELD.ALL],
        max_count=1000,
    )
    if ret != ft.RET_OK:
        raise RuntimeError(str(data))
    frames = [data]
    while page_req_key is not None:
        ret, data, page_req_key = ctx.request_history_kline(
            code,
            start=start,
            end=end,
            ktype=ktype,
            autype=ft.AuType.QFQ,
            fields=[ft.KL_FIELD.ALL],
            max_count=1000,
            page_req_key=page_req_key,
        )
        if ret != ft.RET_OK:
            raise RuntimeError(str(data))
        frames.append(data)
    if frames:
        return pd.concat(frames, ignore_index=True)
    return pd.DataFrame(
        columns=["time_key", "open", "high", "low", "close", "volume", "turnover"]
    )


def download(
    root: str,
    codes: List[Tuple[str, str, str]],
    periods: List[str],
    start: str | None,
    end: str | None,
    host: str,
    port: int,
):
    ctx = ft.OpenQuoteContext(host=host, port=port)
    try:
        for market, symbol, order_book_id in codes:
            futu_code = f"{market}.{symbol}"
            for p in periods:
                df = fetch_history(ctx, futu_code, p, start, end)
                save_csv(root, order_book_id, p, df)
    finally:
        ctx.close()


def parse_args(argv: List[str] | None = None):
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--data-dir",
        type=str,
        default=os.getenv("FUTU_DATA_PATH", os.path.join(os.getcwd(), "data")),
    )
    parser.add_argument("--codes", type=str, default="")
    parser.add_argument("--code-file", type=str, default="")
    parser.add_argument("--periods", type=str, default="1m,3m,5m,1d,1w,1mo")
    parser.add_argument("--start", type=str, default=None)
    parser.add_argument("--end", type=str, default=None)
    parser.add_argument("--host", type=str, default=os.getenv("FUTU_HOST", FUTU_HOST))
    parser.add_argument(
        "--port", type=int, default=int(os.getenv("FUTU_PORT", str(FUTU_PORT)))
    )
    return parser.parse_args(argv)


def main(argv: List[str] | None = None):
    args = parse_args(argv)
    print(args)
    codes_raw: List[str] = []
    if args.codes:
        codes_raw.extend([x for x in args.codes.split(",") if x])
    if args.code_file:
        with open(args.code_file, "r", encoding="utf-8") as f:
            codes_raw.extend([line.strip() for line in f if line.strip()])
    codes = parse_codes(codes_raw)
    validate_time(args.start)
    validate_time(args.end)
    periods = [p.strip() for p in args.periods.split(",") if p.strip()]
    for p in periods:
        if p not in PERIOD_MAP:
            raise ValueError("unsupported period")
    download(args.data_dir, codes, periods, args.start, args.end, args.host, args.port)


if __name__ == "__main__":
    main()
