import typer
import yaml
import json
import httpx
import tarfile
import os
import io
from pathlib import Path
from rich.console import Console
from rich.panel import Panel
from rich.syntax import Syntax

from ...contracts.uac import CharmConfig
from ..config import get_token, load_config
from ..git import get_repo_info, GitError

console = Console()

# [Update] 指向 Next.js Store API (本地開發預設值)
# 若上線後，請將此處改為 https://your-store-domain.com/api
DEFAULT_API_BASE = "http://localhost:3000/api"

# Files to exclude from the bundle for security and size.
IGNORE_SET = {
    ".env", ".env.local", "secrets.json", 
    ".git", ".DS_Store", "__pycache__",  
    "venv", ".venv", "dist", "build"      
}

def is_ignored(name: str) -> bool:
    if name in IGNORE_SET: return True
    if name.endswith(('.pyc', '.pyo', '.pyd')): return True
    return False

def create_bundle(source_dir: Path) -> bytes:
    file_stream = io.BytesIO()
    with tarfile.open(fileobj=file_stream, mode="w:gz") as tar:
        for root, dirs, files in os.walk(source_dir):
            dirs[:] = [d for d in dirs if not is_ignored(d)] 
            for file in files:
                if is_ignored(file): continue
                full_path = Path(root) / file
                arcname = full_path.relative_to(source_dir)
                tar.add(full_path, arcname=str(arcname))
    file_stream.seek(0)
    return file_stream.getvalue()

def push_command(
    path: str = typer.Argument(".", help="Path to the Charm project root"),
    dry_run: bool = typer.Option(False, "--dry-run", help="Preview payload without sending"),
    api_base_override: str = typer.Option(None, "--api-base", help="Override API base URL")
):
    """
    Register/Publish the Agent to the Charm Registry.
    Uploads source code bundle and links metadata.
    """
    project_path = Path(path).resolve()
    
    token = get_token()
    if not token:
        console.print("[bold red]Auth Error:[/bold red] Please run [bold]charm auth[/bold] first.")
        raise typer.Exit(code=1)

    yaml_file = project_path / "charm.yaml"
    if not yaml_file.exists():
        console.print(f"[bold red]Error:[/bold red] charm.yaml not found in {project_path}")
        raise typer.Exit(code=1)

    try:
        with open(yaml_file, "r") as f:
            uac_payload = yaml.safe_load(f)
            config = CharmConfig(**uac_payload)
            uac_payload = config.model_dump(mode='json', exclude_none=True)
    except Exception as e:
        console.print(f"[bold red]Config Error:[/bold red] {e}")
        raise typer.Exit(code=1)

    try:
        repo_info = get_repo_info(project_path)
    except GitError:
        repo_info = {"url": "", "branch": "main", "commit": "unknown", "is_dirty": "False"}

    if repo_info.get("is_dirty") == "True" and not dry_run:
        console.print("[bold yellow]Warning:[/bold yellow] You have uncommitted changes. Uploading local files anyway.")

    metadata_payload = {
        "uac": uac_payload,
        "repo": {
            "url": repo_info["url"],
            "branch": repo_info["branch"],
            "commit": repo_info["commit"]
        }
    }

    with console.status("[bold green]Bundling source code...[/bold green]"):
        bundle_bytes = create_bundle(project_path)
        bundle_size_kb = len(bundle_bytes) / 1024

    if dry_run:
        console.print(f"\n[bold blue]Dry Run:[/bold blue] Bundle Size: {bundle_size_kb:.2f} KB")
        console.print(Syntax(json.dumps(metadata_payload, indent=2), "json", theme="monokai"))
        raise typer.Exit(code=0)

    config_data = load_config()
    
    api_base = api_base_override or config_data.get("core", {}).get("api_base") or DEFAULT_API_BASE
    
    api_base = api_base.rstrip("/")
    
    target_url = f"{api_base}/v1/agents"

    console.print(f" Pushing to [underline]{target_url}[/underline]...")

    try:
        with console.status("[bold green]Uploading Bundle & Metadata...[/bold green]"):
            files = {
                "file": ("source.tar.gz", bundle_bytes, "application/gzip")
            }
            data = {
                "metadata": json.dumps(metadata_payload)
            }
            
            response = httpx.post(
                target_url,
                files=files,
                data=data,
                headers={"Authorization": f"Bearer {token}"},
                timeout=60.0
            )

        if response.status_code in [200, 201]:
            resp_data = response.json()
            agent_url = resp_data.get("url", "N/A")
            
            agent_version = getattr(config.persona, "version", "0.1.0")

            console.print(Panel(
                f"[bold]Agent:[/bold] {config.persona.name}\n"
                f"[bold]Version:[/bold] {agent_version}\n" 
                f"[bold]Size:[/bold] {bundle_size_kb:.2f} KB\n\n"
                f"🔗 [link={agent_url}]{agent_url}[/link]",
                title="[bold green]✔ Successfully Published[/bold green]",
                border_style="green"
            ))
        else:
            console.print(f"[bold red]Server Error ({response.status_code}):[/bold red]")
            try:
                err_msg = response.json().get("error", response.text)
                console.print(f"[red]{err_msg}[/red]")
            except:
                console.print(response.text)
            raise typer.Exit(code=1)

    except Exception as e:
        console.print(f"[bold red]Connection Error:[/bold red] {e}")
        raise typer.Exit(code=1)