import os
import json
import time
import re
from types import SimpleNamespace
from urllib.parse import quote_plus, urlencode, urlparse, parse_qs
from urllib import error, request
from dotenv import load_dotenv
from sqlalchemy import create_engine
import pandas as pd
from mcp.server.fastmcp import FastMCP

# Load environment variables
load_dotenv()

# Initialize FastMCP server
mcp = FastMCP("mysql-mcp")
_feishu_tat_cache = {"token": "", "expire_at": 0.0, "app_id": "", "app_secret": ""}

def get_connect_args(configs):
    """
    Get database connection arguments, supporting SSL and other generic configurations
    """
    connect_args = {}
    
    # Check if SSL is required
    use_ssl = getattr(configs, 'use_ssl', False)
    
    # TiDB defaults to using SSL if specified or if db_type is tidb (though here we simplify)
    if use_ssl:
        ssl_ca = getattr(configs, 'ssl_ca', '')
        if ssl_ca:
            connect_args = {
                'ssl': {
                    'ca': ssl_ca,
                    'check_hostname': False,
                    'verify_identity': False
                }
            }
    
    return connect_args

def get_mysql_config():
    """Create a config object from environment variables."""
    return SimpleNamespace(
        host=os.getenv("MYSQL_HOST", "localhost"),
        port=int(os.getenv("MYSQL_PORT", 3306)),
        user=os.getenv("MYSQL_USER", "root"),
        password=os.getenv("MYSQL_PASSWORD", ""),
        database=os.getenv("MYSQL_DATABASE", ""),
        use_ssl=os.getenv("MYSQL_USE_SSL", "false").lower() == "true",
        ssl_ca=os.getenv("MYSQL_SSL_CA", ""),
    )

def get_feishu_config():
    return SimpleNamespace(
        mcp_url=os.getenv("FEISHU_MCP_URL", "https://mcp.feishu.cn/mcp"),
        open_api_url=os.getenv("FEISHU_OPEN_API_URL", "https://open.feishu.cn/open-apis"),
        app_id=os.getenv("FEISHU_MCP_APP_ID", ""),
        app_secret=os.getenv("FEISHU_MCP_APP_SECRET", ""),
        allowed_tools=os.getenv("FEISHU_MCP_ALLOWED_TOOLS", ""),
    )

def get_feishu_tat(configs):
    app_id = configs.app_id.strip()
    app_secret = configs.app_secret.strip()
    if not app_id or not app_secret:
        raise ValueError("Missing Feishu app credential. Set FEISHU_MCP_APP_ID and FEISHU_MCP_APP_SECRET.")

    now = time.time()
    if (
        _feishu_tat_cache["token"]
        and _feishu_tat_cache["expire_at"] > now + 60
        and _feishu_tat_cache["app_id"] == app_id
        and _feishu_tat_cache["app_secret"] == app_secret
    ):
        return _feishu_tat_cache["token"]

    token_url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
    body = json.dumps({"app_id": app_id, "app_secret": app_secret}).encode("utf-8")
    req = request.Request(token_url, data=body, headers={"Content-Type": "application/json"}, method="POST")

    try:
        with request.urlopen(req, timeout=30) as resp:
            response_text = resp.read().decode("utf-8")
    except error.HTTPError as e:
        detail = e.read().decode("utf-8", errors="ignore")
        raise RuntimeError(detail or str(e)) from e
    except Exception as e:
        raise RuntimeError(str(e)) from e

    try:
        data = json.loads(response_text)
    except json.JSONDecodeError as e:
        raise RuntimeError(response_text) from e

    if data.get("code") != 0:
        raise RuntimeError(data.get("msg") or data.get("message") or "Failed to get tenant access token.")

    token = data.get("tenant_access_token", "")
    expire = int(data.get("expire", 0))
    if not token:
        raise RuntimeError("Feishu auth response missing tenant_access_token.")

    _feishu_tat_cache["token"] = token
    _feishu_tat_cache["expire_at"] = now + max(expire, 0)
    _feishu_tat_cache["app_id"] = app_id
    _feishu_tat_cache["app_secret"] = app_secret
    return token

def build_feishu_headers(configs, allowed_tools: str = ""):
    headers = {"Content-Type": "application/json"}
    headers["X-Lark-MCP-TAT"] = get_feishu_tat(configs)

    resolved_allowed_tools = allowed_tools.strip() if allowed_tools else configs.allowed_tools.strip()
    if resolved_allowed_tools:
        tools = [tool.strip() for tool in resolved_allowed_tools.split(",") if tool.strip()]
        if tools:
            headers["X-Lark-MCP-Allowed-Tools"] = ",".join(tools)
    return headers

def call_feishu_rpc(method: str, params: dict | None, configs, allowed_tools: str = "") -> dict:
    payload = {"jsonrpc": "2.0", "id": 1, "method": method}
    if params is not None:
        payload["params"] = params
    body = json.dumps(payload).encode("utf-8")
    headers = build_feishu_headers(configs, allowed_tools=allowed_tools)
    req = request.Request(configs.mcp_url, data=body, headers=headers, method="POST")

    try:
        with request.urlopen(req, timeout=30) as resp:
            response_text = resp.read().decode("utf-8")
    except error.HTTPError as e:
        detail = e.read().decode("utf-8", errors="ignore")
        return {"jsonrpc": "2.0", "id": 1, "error": {"code": e.code, "message": detail or str(e)}}
    except Exception as e:
        return {"jsonrpc": "2.0", "id": 1, "error": {"code": -1, "message": str(e)}}

    try:
        return json.loads(response_text)
    except json.JSONDecodeError:
        return {"jsonrpc": "2.0", "id": 1, "error": {"code": -2, "message": response_text}}

def _col_num_to_letter(n: int) -> str:
    result = ""
    while n > 0:
        n, remainder = divmod(n - 1, 26)
        result = chr(65 + remainder) + result
    return result

def _build_open_api_url(configs, path: str, query: dict | None = None) -> str:
    base = configs.open_api_url.rstrip("/")
    url = f"{base}/{path.lstrip('/')}"
    if query:
        encoded = urlencode(query)
        if encoded:
            url = f"{url}?{encoded}"
    return url

def _feishu_open_api_request(method: str, path: str, configs, body: dict | None = None, query: dict | None = None) -> dict:
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {get_feishu_tat(configs)}",
    }
    raw = None if body is None else json.dumps(body, ensure_ascii=False).encode("utf-8")
    url = _build_open_api_url(configs, path, query=query)
    req = request.Request(url, data=raw, headers=headers, method=method.upper())
    try:
        with request.urlopen(req, timeout=30) as resp:
            response_text = resp.read().decode("utf-8")
    except error.HTTPError as e:
        detail = e.read().decode("utf-8", errors="ignore")
        return {"code": e.code, "msg": detail or str(e)}
    except Exception as e:
        return {"code": -1, "msg": str(e)}

    try:
        return json.loads(response_text)
    except json.JSONDecodeError:
        return {"code": -2, "msg": response_text}

def _normalize_sheet_values(values):
    normalized = []
    for row in values:
        new_row = []
        for cell in row:
            if cell is None:
                new_row.append("")
            elif isinstance(cell, (str, int, float, bool)):
                new_row.append(cell)
            else:
                new_row.append(json.dumps(cell, ensure_ascii=False))
        normalized.append(new_row)
    return normalized

def _records_to_matrix(records: list[dict]) -> list[list]:
    columns = []
    seen = set()
    for record in records:
        if isinstance(record, dict):
            for key in record.keys():
                if key not in seen:
                    seen.add(key)
                    columns.append(str(key))
    values = [columns]
    for record in records:
        if isinstance(record, dict):
            values.append([record.get(col, "") for col in columns])
    return _normalize_sheet_values(values)

def _sheet_clear(spreadsheet_token: str, sheet_id: str, configs) -> dict:
    read_path = f"/sheets/v2/spreadsheets/{spreadsheet_token}/values/{sheet_id}"
    read_resp = _feishu_open_api_request(
        "GET",
        read_path,
        configs,
        query={"valueRenderOption": "ToString", "dateTimeRenderOption": "FormattedString"},
    )
    if read_resp.get("code") != 0:
        return read_resp

    values = read_resp.get("data", {}).get("valueRange", {}).get("values", [])
    if not values:
        return {"code": 0, "msg": "ok", "data": {"cleared": False}}

    rows = len(values)
    cols = max(len(row) for row in values) if values else 0
    if rows <= 0 or cols <= 0:
        return {"code": 0, "msg": "ok", "data": {"cleared": False}}

    end_col = _col_num_to_letter(cols)
    clear_range = f"{sheet_id}!A1:{end_col}{rows}"
    clear_values = [[""] * cols for _ in range(rows)]
    clear_resp = _feishu_open_api_request(
        "PUT",
        f"/sheets/v2/spreadsheets/{spreadsheet_token}/values",
        configs,
        body={"valueRange": {"range": clear_range, "values": clear_values}},
    )
    if clear_resp.get("code") == 0:
        clear_resp["clear_range"] = clear_range
    return clear_resp

def _get_bitable_fields(app_token: str, table_id: str, configs) -> set[str]:
    page_token = ""
    fields = set()
    while True:
        query = {"page_size": 500}
        if page_token:
            query["page_token"] = page_token
        resp = _feishu_open_api_request(
            "GET",
            f"/bitable/v1/apps/{app_token}/tables/{table_id}/fields",
            configs,
            query=query,
        )
        if resp.get("code") != 0:
            return set()
        items = resp.get("data", {}).get("items", [])
        for item in items:
            field_name = item.get("field_name")
            if field_name:
                fields.add(field_name)
        has_more = resp.get("data", {}).get("has_more", False)
        if not has_more:
            break
        page_token = resp.get("data", {}).get("page_token", "")
        if not page_token:
            break
    return fields

def execute_query(sql, configs):
    """
    Execute SQL query and return DataFrame
    """
    try:
        cf_port = configs.port
    except:
        cf_port = 3306
        
    # URL-encode the password to handle special characters
    password_encoded = quote_plus(configs.password)
    
    # Get connection arguments
    connect_args = get_connect_args(configs)
    
    # Create engine
    connection_string = f'mysql+pymysql://{configs.user}:{password_encoded}@{configs.host}:{cf_port}/{configs.database}'
    engine = create_engine(connection_string, connect_args=connect_args)
    
    # Connect and read
    conn = engine.raw_connection()
    try:
        df = pd.read_sql(sql, conn)
        return df
    finally:
        conn.close()

def _get_first_sheet_id(spreadsheet_token: str, configs) -> str:
    """
    Get the first sheet id of a spreadsheet.
    """
    path = f"/sheets/v3/spreadsheets/{spreadsheet_token}/sheets/query"
    resp = _feishu_open_api_request("GET", path, configs)
    if resp.get("code") != 0:
        raise RuntimeError(f"Failed to get sheets: {resp}")
    
    sheets = resp.get("data", {}).get("sheets", [])
    if not sheets:
        raise RuntimeError("No sheets found in the spreadsheet.")
    
    # Sort by index just in case, though usually returned in order
    sheets.sort(key=lambda x: x.get("index", 0))
    return sheets[0].get("sheet_id", "")

def _get_first_table_id(app_token: str, configs) -> str:
    """
    Get the first table id of a Bitable app.
    """
    path = f"/bitable/v1/apps/{app_token}/tables"
    resp = _feishu_open_api_request("GET", path, configs)
    if resp.get("code") != 0:
        raise RuntimeError(f"Failed to get tables: {resp}")
    
    items = resp.get("data", {}).get("items", [])
    if not items:
        raise RuntimeError("No tables found in the bitable app.")
    
    return items[0].get("table_id", "")

def _parse_feishu_url(token_or_url: str) -> tuple[str, str]:
    """
    Parse a Feishu token or URL (Spreadsheet or Bitable).
    Returns (token, id).
    """
    text = token_or_url.strip()
    if not text.startswith("http://") and not text.startswith("https://"):
        return text, ""
    
    try:
        parsed = urlparse(text)
        path_parts = parsed.path.split('/')
        token = ""
        obj_id = ""
        
        # Check for sheets or base (bitable)
        for i, part in enumerate(path_parts):
            if part in ("sheets", "base") and i + 1 < len(path_parts):
                token = path_parts[i+1]
                break
        
        if not token:
            return text, ""
            
        qs = parse_qs(parsed.query)
        # For sheets, it's 'sheet'; for base, it's 'table'
        if "sheet" in qs:
            obj_id = qs["sheet"][0]
        elif "table" in qs:
            obj_id = qs["table"][0]
            
        return token, obj_id
    except Exception:
        return text, ""

@mcp.tool()
def read_sql(query_sql: str) -> str:
    """
    Execute a read-only SQL query on the configured MySQL database.
    Only SELECT and SHOW statements are allowed for safety.
    
    Args:
        query_sql: The SQL query to execute (e.g., "SELECT * FROM users LIMIT 10")
        
    Returns:
        A JSON string representation of the query result.
    """
    # Basic security check for read-only
    normalized_sql = query_sql.strip().upper()
    if not (normalized_sql.startswith("SELECT") or normalized_sql.startswith("SHOW")):
        return "Error: Only SELECT and SHOW statements are allowed in this read-only tool."

    try:
        config = get_mysql_config()
        
        df = execute_query(query_sql, config)
        
        if df is None or df.empty:
            return "[]"
            
        # Convert DataFrame to JSON string
        # orient='records' creates a list of dicts: [{'col1': val1, 'col2': val2}, ...]
        result_json = df.to_json(orient="records", date_format="iso", force_ascii=False)
        return result_json
    except Exception as e:
        return f"Error executing query: {str(e)}"

@mcp.tool()
def feishu_list_tools(allowed_tools: str = "") -> str:
    try:
        config = get_feishu_config()
        init_result = call_feishu_rpc("initialize", None, config, allowed_tools=allowed_tools)
        if init_result.get("error"):
            return json.dumps(init_result, ensure_ascii=False)

        result = call_feishu_rpc("tools/list", None, config, allowed_tools=allowed_tools)
        return json.dumps(result, ensure_ascii=False)
    except Exception as e:
        return f"Error listing Feishu tools: {str(e)}"

@mcp.tool()
def feishu_call_tool(tool_name: str, arguments_json: str = "{}", allowed_tools: str = "") -> str:
    if not tool_name or not tool_name.strip():
        return "Error: tool_name is required."

    try:
        arguments = json.loads(arguments_json)
    except json.JSONDecodeError:
        return "Error: arguments_json must be valid JSON."

    if not isinstance(arguments, dict):
        return "Error: arguments_json must be a JSON object."

    try:
        config = get_feishu_config()
        init_result = call_feishu_rpc("initialize", None, config, allowed_tools=allowed_tools)
        if init_result.get("error"):
            return json.dumps(init_result, ensure_ascii=False)

        params = {"name": tool_name.strip(), "arguments": arguments}
        result = call_feishu_rpc("tools/call", params, config, allowed_tools=allowed_tools)
        return json.dumps(result, ensure_ascii=False)
    except Exception as e:
        return f"Error calling Feishu tool: {str(e)}"

@mcp.tool()
def feishu_read_sheet(spreadsheet_token: str, sheet_id: str = "", range_ref: str = "") -> str:
    """
    Read data from a Feishu spreadsheet.
    
    Args:
        spreadsheet_token: The token of the spreadsheet or the full URL (e.g. https://.../sheets/TOKEN?sheet=ID).
        sheet_id: The ID of the sheet to read. If not provided, it will be parsed from the URL or default to the first sheet.
        range_ref: The range to read (e.g. "A1:B10"). If not provided, reads the entire used range.
    """
    if not spreadsheet_token.strip():
        return "Error: spreadsheet_token is required."

    # Parse potential URL
    spreadsheet_token, parsed_sheet_id = _parse_feishu_url(spreadsheet_token)
    if not sheet_id.strip() and parsed_sheet_id:
        sheet_id = parsed_sheet_id

    try:
        config = get_feishu_config()
        if not sheet_id.strip():
            try:
                sheet_id = _get_first_sheet_id(spreadsheet_token.strip(), config)
            except Exception as e:
                return f"Error getting default sheet: {str(e)}"
        
        if not sheet_id.strip():
            return "Error: sheet_id is required and could not be determined automatically."

        target_range = range_ref.strip() if range_ref.strip() else sheet_id.strip()
        path = f"/sheets/v2/spreadsheets/{spreadsheet_token.strip()}/values/{target_range}"
        result = _feishu_open_api_request(
            "GET",
            path,
            config,
            query={"valueRenderOption": "ToString", "dateTimeRenderOption": "FormattedString"},
        )
        return json.dumps(result, ensure_ascii=False)
    except Exception as e:
        return f"Error reading Feishu sheet: {str(e)}"

@mcp.tool()
def feishu_write_sheet(
    spreadsheet_token: str,
    values_json: str,
    sheet_id: str = "",
    start_cell: str = "A1",
    clear_sheet: bool = True,
) -> str:
    """
    Write data to a Feishu spreadsheet.
    
    Args:
        spreadsheet_token: The token of the spreadsheet or the full URL.
        values_json: The data to write as a JSON string (2D array or array of objects).
        sheet_id: The ID of the sheet to write to. If not provided, it will be parsed from the URL or default to the first sheet.
        start_cell: The starting cell (e.g. "A1").
        clear_sheet: Whether to clear the sheet before writing (default True).
    """
    if not spreadsheet_token.strip():
        return "Error: spreadsheet_token is required."
    
    # Parse potential URL
    spreadsheet_token, parsed_sheet_id = _parse_feishu_url(spreadsheet_token)
    if not sheet_id.strip() and parsed_sheet_id:
        sheet_id = parsed_sheet_id

    # sheet_id is optional now
    if not values_json.strip():
        return "Error: values_json is required."

    try:
        payload = json.loads(values_json)
    except json.JSONDecodeError:
        return "Error: values_json must be valid JSON."

    if isinstance(payload, list) and payload and isinstance(payload[0], dict):
        values = _records_to_matrix(payload)
    elif isinstance(payload, list) and all(isinstance(row, list) for row in payload):
        values = _normalize_sheet_values(payload)
    else:
        return "Error: values_json must be a 2D array or an array of objects."

    if not values:
        return "Error: values_json cannot be empty."

    cell_match = re.match(r"^([A-Za-z]+)(\d+)$", start_cell.strip())
    if not cell_match:
        return "Error: start_cell must be like A1."

    start_col = cell_match.group(1).upper()
    start_row = int(cell_match.group(2))
    start_col_num = 0
    for ch in start_col:
        start_col_num = start_col_num * 26 + (ord(ch) - ord("A") + 1)

    try:
        config = get_feishu_config()
        
        # If sheet_id is missing, get the default one
        if not sheet_id or not sheet_id.strip():
            try:
                sheet_id = _get_first_sheet_id(spreadsheet_token.strip(), config)
            except Exception as e:
                return f"Error getting default sheet: {str(e)}"

        if not sheet_id.strip():
             return "Error: sheet_id is required."

        rows = len(values)
        cols = max(len(row) for row in values)
        normalized_values = [row + [""] * (cols - len(row)) for row in values]
        end_col = _col_num_to_letter(start_col_num + cols - 1)
        end_row = start_row + rows - 1
        write_range = f"{sheet_id.strip()}!{start_col}{start_row}:{end_col}{end_row}"

        clear_result = None
        if clear_sheet:
            clear_result = _sheet_clear(spreadsheet_token.strip(), sheet_id.strip(), config)
        write_result = _feishu_open_api_request(
            "PUT",
            f"/sheets/v2/spreadsheets/{spreadsheet_token.strip()}/values",
            config,
            body={"valueRange": {"range": write_range, "values": normalized_values}},
        )
        output = {"code": write_result.get("code"), "msg": write_result.get("msg"), "data": write_result.get("data"), "write_range": write_range}
        if clear_result is not None:
            output["clear_result"] = clear_result
        return json.dumps(output, ensure_ascii=False)
    except Exception as e:
        return f"Error writing Feishu sheet: {str(e)}"

@mcp.tool()
def feishu_read_bitable(app_token: str, table_id: str = "", page_size: int = 500) -> str:
    if not app_token.strip():
        return "Error: app_token is required."

    # Parse potential URL
    app_token, parsed_table_id = _parse_feishu_url(app_token)
    if not table_id.strip() and parsed_table_id:
        table_id = parsed_table_id

    if page_size <= 0:
        page_size = 500
    page_size = min(page_size, 500)

    try:
        config = get_feishu_config()

        if not table_id.strip():
             try:
                 table_id = _get_first_table_id(app_token.strip(), config)
             except Exception as e:
                 return f"Error getting default table: {str(e)}"

        if not table_id.strip():
            return "Error: table_id is required."

        all_items = []
        page_token = ""
        while True:
            query = {"page_size": page_size}
            if page_token:
                query["page_token"] = page_token
            result = _feishu_open_api_request(
                "GET",
                f"/bitable/v1/apps/{app_token.strip()}/tables/{table_id.strip()}/records",
                config,
                query=query,
            )
            if result.get("code") != 0:
                return json.dumps(result, ensure_ascii=False)
            items = result.get("data", {}).get("items", [])
            all_items.extend(items)
            has_more = result.get("data", {}).get("has_more", False)
            if not has_more:
                break
            page_token = result.get("data", {}).get("page_token", "")
            if not page_token:
                break

        return json.dumps({"code": 0, "msg": "ok", "data": {"items": all_items, "total": len(all_items)}}, ensure_ascii=False)
    except Exception as e:
        return f"Error reading Feishu bitable: {str(e)}"

@mcp.tool()
def feishu_write_bitable(app_token: str, records_json: str, table_id: str = "", clear_table: bool = False) -> str:
    if not app_token.strip():
        return "Error: app_token is required."
    
    # Parse potential URL
    app_token, parsed_table_id = _parse_feishu_url(app_token)
    if not table_id.strip() and parsed_table_id:
        table_id = parsed_table_id

    if not records_json.strip():
        return "Error: records_json is required."

    try:
        raw_records = json.loads(records_json)
    except json.JSONDecodeError:
        return "Error: records_json must be valid JSON."

    if not isinstance(raw_records, list):
        return "Error: records_json must be an array of objects."
    if any(not isinstance(item, dict) for item in raw_records):
        return "Error: each item in records_json must be an object."

    try:
        config = get_feishu_config()
        
        if not table_id.strip():
             try:
                 table_id = _get_first_table_id(app_token.strip(), config)
             except Exception as e:
                 return f"Error getting default table: {str(e)}"
                 
        if not table_id.strip():
            return "Error: table_id is required."

        valid_fields = _get_bitable_fields(app_token.strip(), table_id.strip(), config)
        if not valid_fields:
            return json.dumps({"code": -1, "msg": "Failed to fetch bitable fields."}, ensure_ascii=False)

        delete_count = 0
        if clear_table:
            page_token = ""
            record_ids = []
            while True:
                query = {"page_size": 500}
                if page_token:
                    query["page_token"] = page_token
                list_resp = _feishu_open_api_request(
                    "GET",
                    f"/bitable/v1/apps/{app_token.strip()}/tables/{table_id.strip()}/records",
                    config,
                    query=query,
                )
                if list_resp.get("code") != 0:
                    return json.dumps(list_resp, ensure_ascii=False)
                items = list_resp.get("data", {}).get("items", [])
                record_ids.extend([item.get("record_id") for item in items if item.get("record_id")])
                has_more = list_resp.get("data", {}).get("has_more", False)
                if not has_more:
                    break
                page_token = list_resp.get("data", {}).get("page_token", "")
                if not page_token:
                    break

            for i in range(0, len(record_ids), 500):
                batch_ids = record_ids[i:i + 500]
                del_resp = _feishu_open_api_request(
                    "POST",
                    f"/bitable/v1/apps/{app_token.strip()}/tables/{table_id.strip()}/records/batch_delete",
                    config,
                    body={"records": batch_ids},
                )
                if del_resp.get("code") != 0:
                    return json.dumps(del_resp, ensure_ascii=False)
                delete_count += len(batch_ids)

        prepared_records = []
        ignored_fields = set()
        for record in raw_records:
            fields = {}
            for key, value in record.items():
                field_name = str(key)
                if field_name not in valid_fields:
                    ignored_fields.add(field_name)
                    continue
                if value is None:
                    continue
                if isinstance(value, (str, int, float, bool)):
                    fields[field_name] = value
                else:
                    fields[field_name] = json.dumps(value, ensure_ascii=False)
            prepared_records.append({"fields": fields})

        write_count = 0
        batch_results = []
        for i in range(0, len(prepared_records), 500):
            batch = prepared_records[i:i + 500]
            if not batch:
                continue
            write_resp = _feishu_open_api_request(
                "POST",
                f"/bitable/v1/apps/{app_token.strip()}/tables/{table_id.strip()}/records/batch_create",
                config,
                body={"records": batch},
            )
            batch_results.append(write_resp)
            if write_resp.get("code") != 0:
                return json.dumps({"code": write_resp.get("code"), "msg": write_resp.get("msg"), "failed_batch_index": i // 500 + 1}, ensure_ascii=False)
            write_count += len(batch)

        output = {
            "code": 0,
            "msg": "ok",
            "data": {
                "input_count": len(raw_records),
                "write_count": write_count,
                "delete_count": delete_count,
                "ignored_fields": sorted(ignored_fields),
                "batch_results": batch_results,
            },
        }
        return json.dumps(output, ensure_ascii=False)
    except Exception as e:
        return f"Error writing Feishu bitable: {str(e)}"

def main():
    """Entry point for the package."""
    mcp.run()

if __name__ == "__main__":
    main()
