from __future__ import annotations

import html
import threading
import webbrowser
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import parse_qs

from ..config import ConfigManager


_PROVIDER_PRESETS: dict[str, dict[str, str]] = {
    "openai": {
        "label": "OpenAI",
        "api_key_label": "OpenAI API Key",
        "type": "openai",
        "default_model": "gpt-4.1-mini",
        "description": "ChatGPT-style API with the built-in OpenAI provider.",
    },
    "gemini": {
        "label": "Gemini",
        "api_key_label": "Gemini API Key",
        "type": "gemini",
        "default_model": "gemini-2.5-flash",
        "description": "Google Gemini models through the native Gemini provider.",
    },
    "claude": {
        "label": "Claude",
        "api_key_label": "Claude API Key",
        "type": "claude",
        "default_model": "claude-3-7-sonnet-latest",
        "description": "Anthropic Claude models through the built-in Claude provider.",
    },
}


def _parse_ids(raw: str) -> list[int]:
    result = []
    for chunk in raw.replace("\n", ",").split(","):
        item = chunk.strip()
        if item and item.lstrip("-").isdigit():
            result.append(int(item))
    return result


def _config_telegram(config: dict) -> dict:
    return config.get("telegram", {}) or {}


def _config_provider_items(config: dict) -> dict[str, dict]:
    providers = config.get("providers", {}) or {}
    items = providers.get("items", {}) or {}
    return items if isinstance(items, dict) else {}


def _provider_entry(config: dict, name: str) -> dict:
    entry = _config_provider_items(config).get(name, {}) or {}
    return entry if isinstance(entry, dict) else {}


def _default_provider(config: dict, allowed: set[str] | None = None) -> str:
    provider = str((config.get("providers", {}) or {}).get("default", "openai") or "openai")
    allowed_names = allowed or set(_PROVIDER_PRESETS)
    if provider in allowed_names:
        return provider
    return "openai" if "openai" in allowed_names else next(iter(allowed_names))


def _provider_label(name: str) -> str:
    preset = _PROVIDER_PRESETS.get(name, {})
    return str(preset.get("label", name.title()))


def _provider_model(config: dict, name: str) -> str:
    preset = _PROVIDER_PRESETS.get(name, {})
    return str(_provider_entry(config, name).get("default_model") or preset.get("default_model", ""))


def _provider_api_key(config: dict, name: str) -> str:
    return str(_provider_entry(config, name).get("api_key", ""))


def _provider_base_url(config: dict, name: str) -> str:
    return str(_provider_entry(config, name).get("base_url", ""))


def _messages_html(*, notice: str = "", errors: list[str] | None = None) -> str:
    chunks: list[str] = []
    if notice:
        chunks.append(f"<div class='flash flash-ok'>{html.escape(notice)}</div>")
    if errors:
        items = "".join(f"<li>{html.escape(item)}</li>" for item in errors)
        chunks.append(f"<div class='flash flash-error'><ul>{items}</ul></div>")
    return "".join(chunks)


def _page_shell(*, title: str, subtitle: str, body: str, notice: str = "", errors: list[str] | None = None) -> str:
    return f"""<!doctype html>
<html>
<head>
  <meta charset='utf-8'>
  <meta name='viewport' content='width=device-width, initial-scale=1'>
  <title>{html.escape(title)}</title>
  <style>
    :root {{
      color-scheme: dark;
      --bg: #09111f;
      --panel: rgba(12, 19, 35, 0.88);
      --panel-2: rgba(16, 24, 43, 0.94);
      --line: rgba(148, 163, 184, 0.18);
      --text: #e5eefb;
      --muted: #9fb0ce;
      --accent: #74c0fc;
      --accent-2: #8b5cf6;
      --ok: #123c2a;
      --ok-line: #2e8b57;
      --error: #43181b;
      --error-line: #dc2626;
    }}
    * {{ box-sizing: border-box; }}
    body {{
      margin: 0;
      min-height: 100vh;
      font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
      background:
        radial-gradient(circle at top right, rgba(116, 192, 252, 0.15), transparent 32%),
        radial-gradient(circle at top left, rgba(139, 92, 246, 0.15), transparent 26%),
        linear-gradient(180deg, #08101d 0%, #0b1324 100%);
      color: var(--text);
    }}
    .page {{ max-width: 1120px; margin: 0 auto; padding: 32px 18px 48px; }}
    .hero {{
      padding: 28px;
      border: 1px solid var(--line);
      border-radius: 28px;
      background: linear-gradient(180deg, rgba(17, 25, 45, 0.94), rgba(11, 18, 32, 0.96));
      box-shadow: 0 20px 48px rgba(0, 0, 0, 0.24);
    }}
    .eyebrow {{
      display: inline-flex;
      align-items: center;
      gap: 8px;
      padding: 6px 12px;
      border-radius: 999px;
      background: rgba(116, 192, 252, 0.12);
      color: #cfe8ff;
      font-size: 12px;
      letter-spacing: 0.08em;
      text-transform: uppercase;
    }}
    h1 {{ margin: 14px 0 10px; font-size: 36px; line-height: 1.06; }}
    .subtitle {{ margin: 0; color: var(--muted); max-width: 760px; line-height: 1.6; }}
    .content {{ margin-top: 22px; display: grid; gap: 18px; }}
    .panel {{
      border: 1px solid var(--line);
      border-radius: 22px;
      padding: 22px;
      background: var(--panel);
      backdrop-filter: blur(10px);
    }}
    .panel h2 {{ margin: 0 0 6px; font-size: 20px; }}
    .panel > p {{ margin: 0 0 18px; color: var(--muted); }}
    .grid {{ display: grid; gap: 16px; grid-template-columns: repeat(2, minmax(0, 1fr)); }}
    .providers {{ display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }}
    .provider-card {{
      border: 1px solid rgba(148, 163, 184, 0.16);
      border-radius: 18px;
      padding: 16px;
      background: var(--panel-2);
    }}
    .provider-choice {{ display: flex; align-items: center; gap: 10px; margin-bottom: 12px; font-weight: 700; }}
    .provider-choice input {{ width: 18px; height: 18px; margin: 0; accent-color: var(--accent); }}
    .provider-card p {{ margin: 0 0 14px; color: var(--muted); min-height: 40px; }}
    label {{ display: block; margin-top: 12px; font-weight: 600; color: #dce8fb; }}
    input, textarea, select {{
      width: 100%;
      margin-top: 6px;
      border-radius: 14px;
      border: 1px solid rgba(148, 163, 184, 0.18);
      padding: 12px 14px;
      background: rgba(4, 10, 22, 0.72);
      color: var(--text);
      font: inherit;
    }}
    textarea {{ min-height: 104px; resize: vertical; }}
    .hint {{ color: var(--muted); font-size: 12px; line-height: 1.45; margin-top: 6px; }}
    .flash {{ border-radius: 16px; padding: 14px 16px; margin-bottom: 18px; }}
    .flash-ok {{ background: var(--ok); border: 1px solid var(--ok-line); color: #ccf0da; }}
    .flash-error {{ background: var(--error); border: 1px solid var(--error-line); color: #fecaca; }}
    .flash-error ul {{ margin: 0; padding-left: 18px; }}
    .actions {{ display: flex; flex-wrap: wrap; gap: 12px; margin-top: 22px; }}
    button {{
      appearance: none;
      border: 0;
      border-radius: 999px;
      padding: 12px 18px;
      font: inherit;
      font-weight: 700;
      cursor: pointer;
    }}
    .btn-primary {{
      color: #04121f;
      background: linear-gradient(135deg, #74c0fc, #8b5cf6 120%);
    }}
    .btn-secondary {{
      color: var(--text);
      background: rgba(148, 163, 184, 0.12);
      border: 1px solid rgba(148, 163, 184, 0.18);
    }}
    .footer-note {{ color: var(--muted); font-size: 13px; margin-top: 14px; }}
    @media (max-width: 860px) {{
      h1 {{ font-size: 30px; }}
      .grid {{ grid-template-columns: 1fr; }}
    }}
  </style>
</head>
<body>
  <div class='page'>
    <section class='hero'>
      <div class='eyebrow'>LiteClaw setup</div>
      <h1>{html.escape(title)}</h1>
      <p class='subtitle'>{html.escape(subtitle)}</p>
    </section>
    <section class='content'>
      <div class='panel'>
        {_messages_html(notice=notice, errors=errors)}
        {body}
      </div>
    </section>
  </div>
</body>
</html>"""


def render_settings_page(config: dict, *, notice: str = "", errors: list[str] | None = None) -> str:
    telegram = _config_telegram(config)
    default_provider = _default_provider(config, allowed={"openai", "gemini", "claude", "openrouter"})
    openai = _provider_entry(config, "openai")
    gemini = _provider_entry(config, "gemini")
    claude = _provider_entry(config, "claude")
    openrouter = _provider_entry(config, "openrouter")
    provider_options = "".join(
        f"<option value='{html.escape(name)}'{' selected' if name == default_provider else ''}>{html.escape(_provider_label(name))}</option>"
        for name in ["openai", "gemini", "claude", "openrouter"]
    )
    body = f"""
      <h2>Settings</h2>
      <p>Edit Telegram access, choose the default provider, and manage provider API keys.</p>
      <form method='post' action='/save'>
        <div class='grid'>
          <div>
            <label>Telegram Bot Token
              <input type='password' name='telegram_bot_token' value='{html.escape(str(telegram.get("bot_token", "")))}' autocomplete='off'>
            </label>
            <label>Owner User IDs
              <input type='text' name='telegram_owner_user_ids' value='{html.escape(",".join(str(x) for x in telegram.get("owner_user_ids", [])))}'>
              <div class='hint'>Comma-separated Telegram user ids.</div>
            </label>
            <label>Allowed User IDs
              <input type='text' name='telegram_allowed_user_ids' value='{html.escape(",".join(str(x) for x in telegram.get("allowed_user_ids", [])))}'>
            </label>
            <label>Allowed Chat IDs
              <input type='text' name='telegram_allowed_chat_ids' value='{html.escape(",".join(str(x) for x in telegram.get("allowed_chat_ids", [])))}'>
            </label>
          </div>
          <div>
            <label>Default Provider
              <select name='default_provider'>{provider_options}</select>
            </label>
            <label>OpenRouter API Key
              <input type='password' name='openrouter_api_key' value='{html.escape(str(openrouter.get("api_key", "")))}' autocomplete='off'>
            </label>
            <label>OpenRouter Base URL
              <input type='text' name='openrouter_base_url' value='{html.escape(str(openrouter.get("base_url", "")))}'>
            </label>
            <label>OpenRouter Default Model
              <input type='text' name='openrouter_default_model' value='{html.escape(str(openrouter.get("default_model", "")))}'>
            </label>
          </div>
        </div>
        <div class='providers'>
          <div class='provider-card'>
            <div class='provider-choice'>OpenAI</div>
            <p>Built-in OpenAI provider with the standard OpenAI API.</p>
            <label>OpenAI API Key
              <input type='password' name='openai_api_key' value='{html.escape(str(openai.get("api_key", "")))}' autocomplete='off'>
            </label>
            <label>OpenAI Default Model
              <input type='text' name='openai_default_model' value='{html.escape(str(openai.get("default_model", "")))}'>
            </label>
          </div>
          <div class='provider-card'>
            <div class='provider-choice'>Gemini</div>
            <p>Built-in Gemini provider for Google models.</p>
            <label>Gemini API Key
              <input type='password' name='gemini_api_key' value='{html.escape(str(gemini.get("api_key", "")))}' autocomplete='off'>
            </label>
            <label>Gemini Default Model
              <input type='text' name='gemini_default_model' value='{html.escape(str(gemini.get("default_model", "")))}'>
            </label>
          </div>
          <div class='provider-card'>
            <div class='provider-choice'>Claude</div>
            <p>Built-in Claude provider for Anthropic models.</p>
            <label>Claude API Key
              <input type='password' name='claude_api_key' value='{html.escape(str(claude.get("api_key", "")))}' autocomplete='off'>
            </label>
            <label>Claude Default Model
              <input type='text' name='claude_default_model' value='{html.escape(str(claude.get("default_model", "")))}'>
            </label>
          </div>
        </div>
        <div class='actions'>
          <button type='submit' class='btn-primary'>Save settings</button>
        </div>
      </form>
    """
    return _page_shell(
        title="OpenClaw Lite Settings",
        subtitle="Update tokens, access lists, and provider settings without editing YAML by hand.",
        body=body,
        notice=notice,
        errors=errors,
    )


def render_onboarding_page(config: dict, *, notice: str = "", errors: list[str] | None = None) -> str:
    telegram = _config_telegram(config)
    default_provider = _default_provider(config)
    cards = []
    for name, preset in _PROVIDER_PRESETS.items():
        checked = " checked" if name == default_provider else ""
        cards.append(
            f"""
            <article class='provider-card'>
              <label class='provider-choice'>
                <input type='radio' name='default_provider' value='{html.escape(name)}'{checked}>
                <span>{html.escape(preset['label'])}</span>
              </label>
              <p>{html.escape(preset['description'])}</p>
              <label>{html.escape(preset['label'])} API Key
                <input type='password' name='{html.escape(name)}_api_key' value='{html.escape(_provider_api_key(config, name))}' autocomplete='off'>
              </label>
              <label>{html.escape(preset['label'])} Default Model
                <input type='text' name='{html.escape(name)}_default_model' value='{html.escape(_provider_model(config, name))}'>
              </label>
            </article>
            """
        )
    body = f"""
      <h2>Quickstart onboard</h2>
      <p>Paste the Telegram bot token, choose your main model provider, add its API key, and LiteClaw can launch right after saving.</p>
      <form method='post' action='/save'>
        <div class='grid'>
          <div>
            <label>Telegram Bot Token
              <input type='password' name='telegram_bot_token' value='{html.escape(str(telegram.get("bot_token", "")))}' autocomplete='off'>
            </label>
            <label>Owner User IDs
              <input type='text' name='telegram_owner_user_ids' value='{html.escape(",".join(str(x) for x in telegram.get("owner_user_ids", [])))}'>
              <div class='hint'>Optional, but recommended so admin commands are immediately available to you.</div>
            </label>
          </div>
          <div>
            <label>Allowed User IDs
              <input type='text' name='telegram_allowed_user_ids' value='{html.escape(",".join(str(x) for x in telegram.get("allowed_user_ids", [])))}'>
            </label>
            <label>Allowed Chat IDs
              <input type='text' name='telegram_allowed_chat_ids' value='{html.escape(",".join(str(x) for x in telegram.get("allowed_chat_ids", [])))}'>
            </label>
            <p class='footer-note'>Choose one provider card below as default. LiteClaw will save the provider key and default model into `config.yaml`.</p>
          </div>
        </div>
        <div class='providers'>
          {''.join(cards)}
        </div>
        <div class='actions'>
          <button type='submit' name='submit_action' value='launch' class='btn-primary'>Save and launch LiteClaw</button>
          <button type='submit' name='submit_action' value='save' class='btn-secondary'>Save draft only</button>
        </div>
        <p class='footer-note'>After launch, keep this terminal open. The web panel shuts down automatically once the runtime starts.</p>
      </form>
    """
    return _page_shell(
        title="LiteClaw onboard",
        subtitle="First-run setup for Telegram, provider selection, API keys, and one-click launch.",
        body=body,
        notice=notice,
        errors=errors,
    )


def _apply_provider(items: dict[str, dict], name: str, *, provider_type: str, api_key: str, default_model: str, base_url: str | None = None) -> None:
    entry = dict(items.get(name, {}) or {})
    entry["type"] = provider_type
    entry["enabled"] = True
    entry["api_key"] = api_key
    entry["default_model"] = default_model
    if base_url is not None:
        entry["base_url"] = base_url
    items[name] = entry


def _apply_shared_form_update(data: dict, form: dict[str, str]) -> dict:
    telegram = data.setdefault("telegram", {})
    telegram["bot_token"] = form.get("telegram_bot_token", "")
    telegram["owner_user_ids"] = _parse_ids(form.get("telegram_owner_user_ids", ""))
    telegram["allowed_user_ids"] = _parse_ids(form.get("telegram_allowed_user_ids", ""))
    telegram["allowed_chat_ids"] = _parse_ids(form.get("telegram_allowed_chat_ids", ""))

    providers = data.setdefault("providers", {})
    items = providers.setdefault("items", {})
    selected_provider = form.get("default_provider") or providers.get("default") or "openai"
    providers["default"] = selected_provider

    _apply_provider(
        items,
        "openai",
        provider_type="openai",
        api_key=form.get("openai_api_key", str((items.get("openai") or {}).get("api_key", ""))),
        default_model=form.get("openai_default_model", "") or str((items.get("openai") or {}).get("default_model", _PROVIDER_PRESETS["openai"]["default_model"])),
    )
    _apply_provider(
        items,
        "gemini",
        provider_type="gemini",
        api_key=form.get("gemini_api_key", str((items.get("gemini") or {}).get("api_key", ""))),
        default_model=form.get("gemini_default_model", "") or str((items.get("gemini") or {}).get("default_model", _PROVIDER_PRESETS["gemini"]["default_model"])),
    )
    _apply_provider(
        items,
        "claude",
        provider_type="claude",
        api_key=form.get("claude_api_key", str((items.get("claude") or {}).get("api_key", ""))),
        default_model=form.get("claude_default_model", "") or str((items.get("claude") or {}).get("default_model", _PROVIDER_PRESETS["claude"]["default_model"])),
    )
    _apply_provider(
        items,
        "openrouter",
        provider_type="openai_compatible",
        api_key=form.get("openrouter_api_key", str((items.get("openrouter") or {}).get("api_key", ""))),
        default_model=form.get("openrouter_default_model", "") or str((items.get("openrouter") or {}).get("default_model", "")),
        base_url=form.get("openrouter_base_url", str((items.get("openrouter") or {}).get("base_url", ""))),
    )
    return data


def update_config_from_form(manager: ConfigManager, form: dict[str, str]) -> dict:
    manager.backup("web-ui")
    _apply_shared_form_update(manager.data, form)
    manager.save()
    return manager.data


def update_onboarding_from_form(manager: ConfigManager, form: dict[str, str]) -> dict:
    manager.backup("onboard")
    _apply_shared_form_update(manager.data, form)
    manager.save()
    return manager.data


def validate_onboarding_form(form: dict[str, str]) -> list[str]:
    errors: list[str] = []
    if not form.get("telegram_bot_token", "").strip():
        errors.append("Telegram Bot Token is required.")
    selected_provider = (form.get("default_provider") or "openai").strip()
    if selected_provider not in _PROVIDER_PRESETS:
        errors.append("Choose a supported default provider.")
        return errors
    api_key = form.get(f"{selected_provider}_api_key", "").strip()
    if not api_key:
        errors.append(f"{_PROVIDER_PRESETS[selected_provider]['api_key_label']} is required.")
    return errors


def _browser_url(host: str, port: int) -> str:
    display_host = "127.0.0.1" if host in {"0.0.0.0", "::"} else host
    return f"http://{display_host}:{port}"


def _try_open_browser(url: str) -> None:
    try:
        webbrowser.open(url, new=1)
    except Exception:
        return


def _run_ui_server(
    config_path: str,
    *,
    host: str,
    port: int,
    page_renderer,
    save_handler,
    launch_handler=None,
    banner: str,
    open_browser: bool,
) -> bool:
    manager = ConfigManager(config_path)
    state = {"launch_requested": False}

    class Handler(BaseHTTPRequestHandler):
        def _send_html(self, body: str, status: int = 200) -> None:
            data = body.encode("utf-8")
            self.send_response(status)
            self.send_header("Content-Type", "text/html; charset=utf-8")
            self.send_header("Content-Length", str(len(data)))
            self.end_headers()
            self.wfile.write(data)

        def do_GET(self) -> None:
            if self.path not in {"/", "/index.html"}:
                self._send_html("<h1>Not Found</h1>", 404)
                return
            manager.reload()
            self._send_html(page_renderer(manager.data))

        def do_POST(self) -> None:
            if self.path != "/save":
                self._send_html("<h1>Not Found</h1>", 404)
                return
            length = int(self.headers.get("Content-Length", "0"))
            raw = self.rfile.read(length).decode("utf-8")
            parsed = {key: values[-1] for key, values in parse_qs(raw, keep_blank_values=True).items()}
            if launch_handler is not None:
                notice, errors, should_launch = launch_handler(manager, parsed)
            else:
                notice, errors, should_launch = save_handler(manager, parsed)
            self._send_html(page_renderer(manager.data, notice=notice, errors=errors))
            if should_launch:
                state["launch_requested"] = True
                threading.Thread(target=server.shutdown, daemon=True).start()

        def log_message(self, format, *args):
            return

    def settings_post(manager: ConfigManager, parsed: dict[str, str]) -> tuple[str, list[str] | None, bool]:
        save_handler(manager, parsed)
        return ("Saved. Use /reload in Telegram or restart runtime.", None, False)

    if launch_handler is None:
        launch_handler = settings_post

    server = ThreadingHTTPServer((host, port), Handler)
    browser_url = _browser_url(host, port)
    try:
        print(f"{banner}: {browser_url}")
        if open_browser:
            threading.Thread(target=_try_open_browser, args=(browser_url,), daemon=True).start()
        server.serve_forever()
    finally:
        server.server_close()
    return bool(state["launch_requested"])


def run_web_ui(config_path: str, host: str = "127.0.0.1", port: int = 8787) -> None:
    _run_ui_server(
        config_path,
        host=host,
        port=port,
        page_renderer=render_settings_page,
        save_handler=update_config_from_form,
        banner="OpenClaw Lite web UI",
        open_browser=False,
    )


def run_onboarding_ui(config_path: str, host: str = "127.0.0.1", port: int = 8787, *, open_browser: bool = True) -> bool:
    def handle_post(manager: ConfigManager, parsed: dict[str, str]) -> tuple[str, list[str] | None, bool]:
        action = parsed.get("submit_action", "save")
        if action == "launch":
            errors = validate_onboarding_form(parsed)
            if errors:
                manager.reload()
                return ("", errors, False)
            update_onboarding_from_form(manager, parsed)
            return ("Saved. Launching LiteClaw now...", None, True)
        update_onboarding_from_form(manager, parsed)
        return ("Saved. Draft updated — launch whenever you're ready.", None, False)

    return _run_ui_server(
        config_path,
        host=host,
        port=port,
        page_renderer=render_onboarding_page,
        save_handler=update_onboarding_from_form,
        launch_handler=handle_post,
        banner="LiteClaw onboard UI",
        open_browser=open_browser,
    )
