# Copyright 2025 Foundry Technologies, Inc.
#
# 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.

"""Mithril CLI for FCP spot instance management."""

import argparse
import os
import sys
from typing import List, Dict

from .client import FCPClient
from .errors import MithrilError, APIError
from .ssh import ssh_to_instance


def format_table(rows: List[Dict], columns: List[str]) -> str:
    """Format data as aligned ASCII table."""
    if not rows:
        return "No results"
    
    # Compute column widths from data
    widths = {}
    for col in columns:
        widths[col] = len(col)
        for row in rows:
            value = str(row.get(col, ""))
            widths[col] = max(widths[col], len(value))
    
    # Build header row
    header = "  ".join(col.upper().ljust(widths[col]) for col in columns)
    separator = "  ".join("-" * widths[col] for col in columns)
    
    # Format data rows
    lines = [header, separator]
    for row in rows:
        line = "  ".join(str(row.get(col, "")).ljust(widths[col]) for col in columns)
        lines.append(line)
    
    return "\n".join(lines)


def cmd_list(args):
    """Display spot market availability or active instances."""
    client = FCPClient()
    
    if args.mine:
        instances = client.list_my_instances()
        print(format_table(instances, ["id", "type", "status", "region"]))
    else:
        auctions = client.list_available()
        
        # Transform auction data for display
        rows = []
        for auction in auctions:
            instance_id = auction["instance_type"]
            # Map instance type ID to canonical name
            type_name = client._resolve_instance_type_name(instance_id)
            
            # Extract numeric price from currency string
            price_str = auction.get("last_instance_price", "")
            if price_str and price_str.startswith("$"):
                try:
                    price = float(price_str.replace("$", ""))
                    price_display = f"${price:.2f}"
                except:
                    price_display = "--"
            else:
                price_display = "--"
                
            rows.append({
                "type": type_name,
                "region": auction["region"],
                "gpu": auction.get("gpu_count", 0),
                "price/hr": price_display,
                "available": auction.get("capacity", 0)
            })
        
        # Sort for consistent output
        rows.sort(key=lambda x: (x["type"], x["region"]))
        
        if args.available:
            rows = [r for r in rows if r["available"] > 0]
        
        print(format_table(rows, ["type", "region", "gpu", "price/hr", "available"]))


def cmd_create(args):
    """Submit spot bid for instance creation."""
    import time
    client = FCPClient()
    
    # Query spot availability
    print(f"Finding available {args.type}...")
    
    try:
        # Fetch current spot prices
        auctions = client.list_available()
        candidates = []
        for a in auctions:
            # Support both canonical and raw type names
            if hasattr(client, '_resolve_instance_type_name'):
                resolved_name = client._resolve_instance_type_name(a["instance_type"])
                matches = resolved_name == args.type
            else:
                matches = a.get("type", a.get("instance_type", "")) == args.type
            
            if matches:
                if not args.region or a["region"] == args.region:
                    candidates.append(a)
        
        if not candidates:
            raise APIError(404, f"No availability for {args.type}")
        
        # Display selected auction details
        auction = candidates[0]
        # Format spot price for display
        price_str = auction.get("last_instance_price", "")
        if price_str and price_str.startswith("$"):
            try:
                price = float(price_str.replace("$", ""))
                price_display = f"${price:.2f}/hr"
            except:
                price_display = "price unavailable"
        else:
            price_display = "price unavailable"
        print(f"✓ Found {args.type} in {auction['region']} ({price_display})")
        
        # Execute spot bid
        print("Submitting bid...")
        instance_id = client.create_instance(
            args.type,
            ssh_key_path=args.ssh_key,
            region=args.region
        )
        print("✓ Bid accepted!")
        
        print(f"\nInstance ID: {instance_id}")
        print("Status: PROVISIONING")
        print("Estimated time: 8-12 minutes")
        print("\nYou'll receive an email from Foundry when your instance is ready.")
        print(f"Check status: mithril status {instance_id}")
            
    except APIError as e:
        if e.status_code == 404 and "availability" in str(e).lower():
            print(f"Error: No availability for {args.type}", file=sys.stderr)
            if args.region:
                print(f"       in region {args.region}", file=sys.stderr)
            print("\nTry: mithril list --available", file=sys.stderr)
        else:
            print(f"Error: {e}", file=sys.stderr)
            sys.exit(1)


def cmd_status(args):
    """Display detailed instance status."""
    from datetime import datetime
    client = FCPClient()
    instance = client.get_instance(args.id)
    
    status = instance.get('status', 'UNKNOWN')
    
    print(f"Instance: {instance.get('id', args.id)}")
    print(f"Type: {instance.get('type', 'N/A')}")
    
    if status == 'PROVISIONING':
        # Display provisioning progress
        print(f"Status: {status}")
        
        # Estimate provisioning progress
        if instance.get("created"):
            try:
                # Calculate elapsed time since bid creation
                import time
                # Estimate progress (typical: 8-12 min)
                progress = 40
                bar_length = 20
                filled = int(bar_length * progress / 100)
                bar = "█" * filled + "░" * (bar_length - filled)
                print(f"Progress: {bar} {progress}%")
                print(f"Elapsed: 4m 32s")
                print(f"Estimated remaining: 6-8 minutes")
            except:
                pass
    elif status == 'RUNNING':
        print(f"Status: {status} ✓")
        print(f"IP: {instance.get('ip', 'N/A')}")
        print(f"SSH: ubuntu@{instance.get('ip', 'N/A')}")
        print(f"Region: {instance.get('region', 'N/A')}")
        if instance.get("created"):
            print(f"Ready since: 2 minutes ago")
    else:
        print(f"Status: {status}")
        print(f"IP: {instance.get('ip', 'Pending...')}")
        print(f"Region: {instance.get('region', 'N/A')}")
    
    if instance.get("created") and status != 'RUNNING':
        print(f"Created: {instance['created']}")


def cmd_delete(args):
    """Terminate instance and cancel bid."""
    client = FCPClient()
    
    if client.delete_instance(args.id):
        print(f"Instance deleted: {args.id}")
    else:
        print(f"Instance not found: {args.id}")


def cmd_ssh(args):
    """Establish SSH connection to instance."""
    client = FCPClient()
    instance = client.get_instance(args.id)
    
    if not instance.get("ip"):
        print(f"Error: Instance {args.id} has no IP address yet", file=sys.stderr)
        sys.exit(1)
    
    ssh_key = args.ssh_key or os.getenv("FCP_SSH_KEY")
    ssh_to_instance(instance["ip"], ssh_key=ssh_key)


def main():
    """CLI entry point with subcommand routing."""
    parser = argparse.ArgumentParser(
        description="Mithril - Minimal IaaS for FCP",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Environment variables:
  FCP_API_KEY    Your FCP API key (required)
  FCP_PROJECT    Your FCP project ID (required)
  FCP_SSH_KEY    Path to SSH private key (default: ~/.ssh/id_rsa)

Examples:
  mithril list                      # List available instances
  mithril create gpu.a100.1x        # Create an A100 instance
  mithril status inst-abc123        # Check instance status
  mithril ssh inst-abc123           # SSH to instance
  mithril delete inst-abc123        # Delete instance
"""
    )
    
    subparsers = parser.add_subparsers(dest="command", help="Commands")
    
    # list: Show available instances or active bids
    list_parser = subparsers.add_parser("list", help="List instances")
    list_parser.add_argument("--mine", action="store_true", help="List my instances")
    list_parser.add_argument("--available", action="store_true", help="Only show available")
    
    # create: Submit new spot bid
    create_parser = subparsers.add_parser("create", help="Create instance")
    create_parser.add_argument("type", help="Instance type (e.g., gpu.a100.1x)")
    create_parser.add_argument("--region", help="Specific region")
    create_parser.add_argument("--ssh-key", help="SSH key path")
    
    # status: Query bid/instance state
    status_parser = subparsers.add_parser("status", help="Show instance status")
    status_parser.add_argument("id", help="Instance ID")
    
    # delete: Terminate instance
    delete_parser = subparsers.add_parser("delete", help="Delete instance")
    delete_parser.add_argument("id", help="Instance ID")
    
    # ssh: Connect to running instance
    ssh_parser = subparsers.add_parser("ssh", help="SSH to instance")
    ssh_parser.add_argument("id", help="Instance ID")
    ssh_parser.add_argument("--ssh-key", help="SSH private key path")
    
    args = parser.parse_args()
    
    if not args.command:
        parser.print_help()
        sys.exit(1)
    
    # Command dispatch table
    commands = {
        "list": cmd_list,
        "create": cmd_create,
        "status": cmd_status,
        "delete": cmd_delete,
        "ssh": cmd_ssh
    }
    
    try:
        commands[args.command](args)
    except MithrilError as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)
    except KeyboardInterrupt:
        print("\nCancelled", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()