#!/usr/bin/env python3
# Copyright 2026 InvestorClaw Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


"""
Interactive LLM Configuration for InvestorClaw.

Claude Code: Guides users through verification subagent setup.
OpenClaw: Guides through provider selection (Local Ollama, NGC, Together.ai, Custom).

Writes configuration to ~/.investorclaw/.env
"""

import json
import logging
import os
import socket
import sys
from pathlib import Path
from typing import Dict, Optional

logging.basicConfig(level=logging.WARNING)
logger = logging.getLogger(__name__)

SKILL_DIR = Path(__file__).parent.parent
ENV_DIR = Path.home() / ".investorclaw"
ENV_FILE = ENV_DIR / ".env"


def is_claude_code() -> bool:
    """Detect if running in Claude Code (vs OpenClaw)."""
    # Claude Code sets ANTHROPIC_CLAUDE_CODE or similar marker
    return os.environ.get("ANTHROPIC_CLAUDE_CODE") is not None


def port_open(host: str, port: int, timeout: float = 2.0) -> bool:
    """Check if a port is open (used to detect local Ollama/llama-server)."""
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(timeout)
        result = sock.connect_ex((host, port))
        sock.close()
        return result == 0
    except Exception:
        return False


def detect_local_ollama() -> Optional[str]:
    """Detect if Ollama is running locally (port 11434)."""
    if port_open("localhost", 11434):
        return "http://localhost:11434"
    return None


def detect_local_llama_server() -> Optional[str]:
    """Detect if llama-server is running locally (port 8080)."""
    if port_open("localhost", 8080):
        return "http://localhost:8080"
    return None


def test_endpoint(endpoint: str, api_key: str = "", model: str = "") -> bool:
    """Test if an endpoint is reachable."""
    try:
        if endpoint.startswith("http://localhost") or endpoint.startswith("http://127.0.0.1"):
            return port_open(
                endpoint.split("://")[1].split(":")[0],
                int(endpoint.split(":")[-1]),
            )
        # For cloud endpoints, just test DNS resolution
        hostname = endpoint.split("://")[1].split(":")[0]
        socket.gethostbyname(hostname)
        return True
    except Exception:
        return False


def write_env(config: Dict[str, str]) -> None:
    """Write configuration to .env file."""
    ENV_DIR.mkdir(parents=True, exist_ok=True)

    # Read existing .env if it exists
    existing = {}
    if ENV_FILE.exists():
        with open(ENV_FILE) as fh:
            for line in fh:
                line = line.strip()
                if line and not line.startswith("#") and "=" in line:
                    k, _, v = line.partition("=")
                    existing[k.strip()] = v.strip()

    # Merge new config
    existing.update(config)

    # Write back
    with open(ENV_FILE, "w") as fh:
        fh.write("# InvestorClaw LLM Configuration\n")
        fh.write("# Auto-generated by llm-config command\n\n")
        for k, v in sorted(existing.items()):
            fh.write(f"{k}={v}\n")

    os.chmod(ENV_FILE, 0o600)
    print(f"\n✅ Configuration saved to {ENV_FILE}")


def setup_claude_code() -> None:
    """Configure verification for Claude Code users."""
    print("\n" + "=" * 60)
    print("InvestorClaw Verification Setup (Claude Code)")
    print("=" * 60)

    print("""
Claude Code users can spawn subagents to verify portfolio analysis:

  ✅ Spot-check financial calculations (yields, durations, returns)
  ✅ Validate Python computations against expected ranges
  ✅ Create HMAC fingerprints for audit trails
  ✅ No setup required — just ask Claude

Example: "Verify this analysis using a subagent"

This is OPTIONAL. Basic portfolio analysis works without verification.
""")

    response = input("Set up verification subagent support? (y/n): ").lower().strip()
    if response == "y":
        config = {
            "INVESTORCLAW_VERIFICATION_ENABLED": "true",
        }
        write_env(config)
        print("\n✅ Verification support enabled. Request: 'Verify using a subagent'")
    else:
        print("\n⏭️  Skipping verification setup.")


def setup_openclaw() -> None:
    """Configure LLM provider for OpenClaw users."""
    print("\n" + "=" * 60)
    print("InvestorClaw Consultation Setup (OpenClaw)")
    print("=" * 60)

    print("""
Choose a provider for consultative LLM (optional verification & synthesis):

  0. Skip (basic analysis only, no consultation)
  1. Local Ollama (free, 24GB+ VRAM required)
  2. Local llama-server (recommended if you have GPU)
  3. NVIDIA NGC (enterprise, NVIDIA employees only)
  4. Custom endpoint (your own cluster)
  5. Together.ai (free tier available, no GPU needed)
""")

    choice = input("Select option (0-5): ").strip()

    if choice == "0":
        config = {"INVESTORCLAW_CONSULTATION_ENABLED": "false"}
        write_env(config)
        print("\n✅ Consultation disabled. Basic analysis still works.")
        return

    if choice == "1":
        setup_ollama()
    elif choice == "2":
        setup_llama_server()
    elif choice == "3":
        setup_ngc()
    elif choice == "4":
        setup_custom()
    elif choice == "5":
        setup_together()
    else:
        print("\n❌ Invalid choice.")
        sys.exit(1)


def setup_ollama() -> None:
    """Set up local Ollama."""
    print("\n📦 Ollama Setup")
    print("-" * 40)

    detected = detect_local_ollama()
    if detected:
        print(f"✅ Ollama detected at {detected}")
        use_detected = input("Use detected Ollama? (y/n): ").lower().strip()
        if use_detected != "y":
            detected = None

    endpoint = (
        detected
        or input("Ollama endpoint (default: http://localhost:11434): ").strip()
        or "http://localhost:11434"
    )

    if not test_endpoint(endpoint):
        print(f"⚠️  Could not reach {endpoint}")
        if input("Continue anyway? (y/n): ").lower().strip() != "y":
            return

    print("\nRecommended models:")
    print("  - gemma4:e4b (high quality, ~30 tok/s, 9.6 GB)")
    print("  - gemma:2b (faster, ~50 tok/s, 1.3 GB)")
    model = input("Model name (default: gemma4:e4b): ").strip() or "gemma4:e4b"

    config = {
        "INVESTORCLAW_CONSULTATION_ENABLED": "true",
        "INVESTORCLAW_CONSULTATION_ENDPOINT": endpoint,
        "INVESTORCLAW_CONSULTATION_MODEL": model,
    }
    write_env(config)


def setup_llama_server() -> None:
    """Set up local llama-server."""
    print("\n🚀 llama-server Setup")
    print("-" * 40)

    detected = detect_local_llama_server()
    if detected:
        print(f"✅ llama-server detected at {detected}")
        use_detected = input("Use detected llama-server? (y/n): ").lower().strip()
        if use_detected != "y":
            detected = None

    endpoint = (
        detected
        or input("llama-server endpoint (default: http://localhost:8080): ").strip()
        or "http://localhost:8080"
    )

    if not test_endpoint(endpoint):
        print(f"⚠️  Could not reach {endpoint}")
        if input("Continue anyway? (y/n): ").lower().strip() != "y":
            return

    print("\nRecommended models (llama-server with 131K context):")
    print("  - gemma4-consult (high quality, ~73 tok/s, 9.6 GB) ⭐")
    print("  - gemma4:e4b (general purpose, ~73 tok/s, 9.6 GB)")
    model = input("Model name (default: gemma4-consult): ").strip() or "gemma4-consult"

    config = {
        "INVESTORCLAW_CONSULTATION_ENABLED": "true",
        "INVESTORCLAW_CONSULTATION_ENDPOINT": endpoint,
        "INVESTORCLAW_CONSULTATION_MODEL": model,
    }
    write_env(config)


def setup_ngc() -> None:
    """Set up NVIDIA NGC."""
    print("\n🎮 NVIDIA NGC Setup")
    print("-" * 40)

    api_key = input("NGC API key (from https://ngc.nvidia.com): ").strip()
    if not api_key:
        print("❌ API key required.")
        return

    endpoint = input(
        "NGC endpoint (default: https://api.ngc.nvidia.com/v2/org/inferences): "
    ).strip()
    endpoint = endpoint or "https://api.ngc.nvidia.com/v2/org/inferences"

    model = input("Model name (default: nemo-b-32b): ").strip() or "nemo-b-32b"

    config = {
        "INVESTORCLAW_CONSULTATION_ENABLED": "true",
        "INVESTORCLAW_CONSULTATION_ENDPOINT": endpoint,
        "INVESTORCLAW_CONSULTATION_API_KEY": api_key,
        "INVESTORCLAW_CONSULTATION_MODEL": model,
    }
    write_env(config)


def setup_custom() -> None:
    """Set up custom endpoint."""
    print("\n🔧 Custom Endpoint Setup")
    print("-" * 40)

    endpoint = input("Endpoint URL (e.g., https://your-cluster:8000/v1): ").strip()
    if not endpoint:
        print("❌ Endpoint URL required.")
        return

    api_key = input("API key (if required, leave blank for none): ").strip()
    model = input("Model name: ").strip()

    if not model:
        print("❌ Model name required.")
        return

    config = {
        "INVESTORCLAW_CONSULTATION_ENABLED": "true",
        "INVESTORCLAW_CONSULTATION_ENDPOINT": endpoint,
        "INVESTORCLAW_CONSULTATION_MODEL": model,
    }
    if api_key:
        config["INVESTORCLAW_CONSULTATION_API_KEY"] = api_key

    write_env(config)


def setup_together() -> None:
    """Set up Together.ai."""
    print("\n☁️  Together.ai Setup")
    print("-" * 40)

    print("""
Together.ai offers free tier (100+ calls/month) and paid options.
Sign up at: https://www.together.ai

Recommended model: google/gemma-4-31B-it
""")

    api_key = input("Together.ai API key: ").strip()
    if not api_key:
        print("❌ API key required.")
        return

    endpoint = "https://api.together.xyz/v1"
    model = (
        input("Model name (default: google/gemma-4-31B-it): ").strip() or "google/gemma-4-31B-it"
    )

    config = {
        "INVESTORCLAW_CONSULTATION_ENABLED": "true",
        "INVESTORCLAW_CONSULTATION_ENDPOINT": endpoint,
        "INVESTORCLAW_CONSULTATION_API_KEY": api_key,
        "INVESTORCLAW_CONSULTATION_MODEL": model,
    }
    write_env(config)


def main() -> int:
    """Main entry point."""
    try:
        # Check if running in non-interactive mode (e.g., from harness)
        if not sys.stdin.isatty():
            print(
                json.dumps(
                    {
                        "status": "skipped",
                        "reason": "LLM config requires interactive mode. Run with -i flag in terminal.",
                        "ic_result": {"script": "llm_config.py", "exit_code": 0},
                    }
                )
            )
            return 0

        if is_claude_code():
            setup_claude_code()
        else:
            setup_openclaw()
        return 0
    except KeyboardInterrupt:
        print("\n\n⏹️  Cancelled.")
        return 1
    except Exception as e:
        print(f"\n❌ Error: {e}")
        logger.exception("llm_config error")
        return 1


if __name__ == "__main__":
    sys.exit(main())
