"""CLI commands for external provider integrations (GitHub, GitLab, Jira).

Provides one-way sync capabilities without webhooks.
"""
from __future__ import annotations

import asyncio
import os

import click

from proxym.cli._groups._shared import make_args as _make_args
from proxym.external.config import get_config_manager, ProviderConfig
from proxym.external.providers import get_provider, ExternalProject, ExternalIssue


# ── External provider group ───────────────────────────────────

@click.group(name="external")
def external_group():
    """Manage external provider integrations (GitHub, GitLab, Jira)."""
    pass


# ── Provider configuration commands ──────────────────────────

@external_group.command(name="configure")
@click.argument("provider", type=click.Choice(["github", "gitlab", "jira"]))
@click.option("--api-key", required=True, help="API token/personal access token")
@click.option("--base-url", default=None, help="Base URL (for self-hosted GitLab or Jira)")
@click.option("--email", default=None, help="Email (required for Jira)")
@click.option("--name", default="default", help="Configuration name (allows multiple accounts)")
@click.pass_context
def external_configure(
    ctx: click.Context,
    provider: str,
    api_key: str,
    base_url: str | None,
    email: str | None,
    name: str,
):
    """Configure an external provider API key."""
    if provider == "jira" and not email:
        click.echo("  Error: Jira requires --email for authentication")
        ctx.exit(1)
        return

    config = ProviderConfig(
        provider=provider,
        api_key=api_key,
        base_url=base_url,
        email=email,
        name=name,
    )

    manager = get_config_manager()
    manager.add_provider(config)

    click.echo(f"  ✓ Configured {provider}:{name}")

    # Test the configuration
    async def _test():
        return await manager.test_provider(provider, name)
    success, message = asyncio.run(_test())
    if success:
        click.echo("  ✓ Credentials valid")
    else:
        click.echo(f"  ✗ Failed: {message}")


@external_group.command(name="list")
@click.option("--provider", type=click.Choice(["github", "gitlab", "jira"]), default=None)
def external_list(provider: str | None):
    """List configured external providers."""
    manager = get_config_manager()
    configs = manager.list_providers(provider)

    if not configs:
        click.echo("  No external providers configured.")
        click.echo("  Use: proxym external configure <provider> --api-key <key>")
        return

    click.echo(f"\n  Configured providers ({len(configs)}):\n")
    for cfg in configs:
        status = "✓ enabled" if cfg.enabled else "✗ disabled"
        base_info = f" ({cfg.base_url})" if cfg.base_url else ""
        click.echo(f"  {cfg.provider:10} {cfg.name:15} {status}{base_info}")
    click.echo()


@external_group.command(name="test")
@click.argument("provider", type=click.Choice(["github", "gitlab", "jira"]))
@click.option("--name", default="default", help="Configuration name")
@click.pass_context
def external_test(ctx: click.Context, provider: str, name: str):
    """Test external provider credentials."""
    manager = get_config_manager()

    config = manager.get_provider(provider, name)
    if not config:
        click.echo(f"  ✗ No configuration found for {provider}:{name}")
        ctx.exit(1)
        return

    click.echo(f"  Testing {provider}:{name}...")
    async def _test():
        return await manager.test_provider(provider, name)
    success, message = asyncio.run(_test())

    if success:
        click.echo(f"  ✓ {message}")
    else:
        click.echo(f"  ✗ {message}")
        ctx.exit(1)


@external_group.command(name="remove")
@click.argument("provider", type=click.Choice(["github", "gitlab", "jira"]))
@click.option("--name", default="default", help="Configuration name")
@click.pass_context
def external_remove(ctx: click.Context, provider: str, name: str):
    """Remove an external provider configuration."""
    manager = get_config_manager()
    success = manager.remove_provider(provider, name)

    if success:
        click.echo(f"  ✓ Removed {provider}:{name}")
    else:
        click.echo(f"  ✗ Configuration not found: {provider}:{name}")
        ctx.exit(1)


# ── Project management commands ──────────────────────────────

@external_group.group(name="project")
def external_project():
    """Manage projects in external providers."""
    pass


@external_project.command(name="list")
@click.argument("provider", type=click.Choice(["github", "gitlab", "jira"]))
@click.option("--name", default="default", help="Configuration name")
@click.pass_context
def external_project_list(ctx: click.Context, provider: str, name: str):
    """List projects from external provider."""
    manager = get_config_manager()
    config = manager.get_provider(provider, name)

    if not config:
        click.echo(f"  ✗ No configuration found for {provider}:{name}")
        click.echo(f"  Run: proxym external configure {provider} --api-key <key>")
        ctx.exit(1)
        return

    async def _list():
        provider_instance = get_provider(
            config.provider,
            config.api_key,
            config.base_url,
            config.email,
        )
        async with provider_instance:
            projects = await provider_instance.list_projects()
            return projects

    try:
        projects = asyncio.run(_list())
    except Exception as e:
        click.echo(f"  ✗ Failed to list projects: {e}")
        ctx.exit(1)
        return

    if not projects:
        click.echo(f"  No projects found in {provider}")
        return

    click.echo(f"\n  Projects in {provider} ({len(projects)}):\n")
    for proj in projects[:20]:  # Limit to 20
        click.echo(f"  {proj.external_id or proj.id:30} {proj.name}")
    if len(projects) > 20:
        click.echo(f"  ... and {len(projects) - 20} more")
    click.echo()


@external_project.command(name="create")
@click.argument("provider", type=click.Choice(["github", "gitlab", "jira"]))
@click.argument("name")
@click.option("--description", default=None, help="Project description")
@click.option("--namespace", default=None, help="Owner/organization (GitHub) or group (GitLab)")
@click.option("--config-name", default="default", help="Configuration name")
@click.pass_context
def external_project_create(
    ctx: click.Context,
    provider: str,
    name: str,
    description: str | None,
    namespace: str | None,
    config_name: str,
):
    """Create a new project in external provider."""
    manager = get_config_manager()
    config = manager.get_provider(provider, config_name)

    if not config:
        click.echo(f"  ✗ No configuration found for {provider}:{config_name}")
        ctx.exit(1)
        return

    async def _create():
        provider_instance = get_provider(
            config.provider,
            config.api_key,
            config.base_url,
            config.email,
        )
        async with provider_instance:
            project = await provider_instance.create_project(name, description, namespace)
            return project

    try:
        project = asyncio.run(_create())
    except Exception as e:
        click.echo(f"  ✗ Failed to create project: {e}")
        ctx.exit(1)
        return

    click.echo(f"  ✓ Created {provider} project: {project.name}")
    click.echo(f"    URL: {project.url}")
    click.echo(f"    ID: {project.external_id or project.id}")


@external_project.command(name="link")
@click.argument("project_ref")  # proxym project id or name
@click.argument("provider", type=click.Choice(["github", "gitlab", "jira"]))
@click.argument("external_project_id")
@click.option("--config-name", default="default", help="Configuration name")
@click.pass_context
def external_project_link(
    ctx: click.Context,
    project_ref: str,
    provider: str,
    external_project_id: str,
    config_name: str,
):
    """Link a proxym project to an external provider project."""
    from proxym.cli._client import make_client

    args = _make_args(ctx)

    # Resolve proxym project
    with make_client(args.proxy, args.key) as c:
        # Get all projects
        r = c.get("/dashboard/projects")
        data = r.json()
        projects = data if isinstance(data, list) else data.get("projects", [])

        project_id = None
        for proj in projects:
            if proj.get("id") == project_ref or proj.get("name") == project_ref:
                project_id = proj.get("id")
                break

        if not project_id:
            click.echo(f"  ✗ Proxym project not found: {project_ref}")
            ctx.exit(1)
            return

        # Store the link in project metadata
        link_key = f"{provider}_external_id"
        r = c.put(
            f"/dashboard/projects/{project_id}",
            json={link_key: external_project_id},
        )
        r.raise_for_status()

    click.echo(f"  ✓ Linked project {project_ref} to {provider}:{external_project_id}")


# ── Issue sync commands ───────────────────────────────────────

@external_group.group(name="issue")
def external_issue():
    """Manage issues/tickets in external providers."""
    pass


@external_issue.command(name="list")
@click.argument("provider", type=click.Choice(["github", "gitlab", "jira"]))
@click.argument("project_id")  # external project ID
@click.option("--state", default="open", type=click.Choice(["open", "closed", "all"]))
@click.option("--config-name", default="default", help="Configuration name")
@click.pass_context
def external_issue_list(
    ctx: click.Context,
    provider: str,
    project_id: str,
    state: str,
    config_name: str,
):
    """List issues from external provider project."""
    manager = get_config_manager()
    config = manager.get_provider(provider, config_name)

    if not config:
        click.echo(f"  ✗ No configuration found for {provider}:{config_name}")
        ctx.exit(1)
        return

    async def _list():
        provider_instance = get_provider(
            config.provider,
            config.api_key,
            config.base_url,
            config.email,
        )
        async with provider_instance:
            issues = await provider_instance.list_issues(project_id, state)
            return issues

    try:
        issues = asyncio.run(_list())
    except Exception as e:
        click.echo(f"  ✗ Failed to list issues: {e}")
        ctx.exit(1)
        return

    if not issues:
        click.echo(f"  No issues found in {project_id}")
        return

    click.echo(f"\n  Issues in {project_id} ({len(issues)}):\n")
    for issue in issues:
        state_icon = "○" if issue.state == "open" else "●"
        click.echo(f"  {state_icon} #{issue.number} {issue.title[:50]}")
    click.echo()


@external_issue.command(name="create")
@click.argument("provider", type=click.Choice(["github", "gitlab", "jira"]))
@click.argument("project_id")
@click.argument("title")
@click.option("--description", default=None, help="Issue description")
@click.option("--labels", default=None, help="Comma-separated labels")
@click.option("--config-name", default="default", help="Configuration name")
@click.pass_context
def external_issue_create(
    ctx: click.Context,
    provider: str,
    project_id: str,
    title: str,
    description: str | None,
    labels: str | None,
    config_name: str,
):
    """Create an issue in external provider."""
    manager = get_config_manager()
    config = manager.get_provider(provider, config_name)

    if not config:
        click.echo(f"  ✗ No configuration found for {provider}:{config_name}")
        ctx.exit(1)
        return

    label_list = [l.strip() for l in labels.split(",")] if labels else None

    async def _create():
        provider_instance = get_provider(
            config.provider,
            config.api_key,
            config.base_url,
            config.email,
        )
        async with provider_instance:
            issue = await provider_instance.create_issue(
                project_id, title, description, label_list
            )
            return issue

    try:
        issue = asyncio.run(_create())
    except Exception as e:
        click.echo(f"  ✗ Failed to create issue: {e}")
        ctx.exit(1)
        return

    click.echo(f"  ✓ Created issue #{issue.number}: {issue.title}")
    click.echo(f"    URL: {issue.url}")


@external_issue.command(name="sync")
@click.argument("ticket_id")  # proxym ticket ID
@click.argument("provider", type=click.Choice(["github", "gitlab", "jira"]))
@click.option("--project-id", default=None, help="External project ID (auto-detected if linked)")
@click.option("--config-name", default="default", help="Configuration name")
@click.option("--labels", default="proxym-sync", help="Comma-separated labels")
@click.pass_context
def external_issue_sync(
    ctx: click.Context,
    ticket_id: str,
    provider: str,
    project_id: str | None,
    config_name: str,
    labels: str,
):
    """Sync a proxym ticket to external provider as an issue."""
    from proxym.cli._client import make_client

    args = _make_args(ctx)
    manager = get_config_manager()
    config = manager.get_provider(provider, config_name)

    if not config:
        click.echo(f"  ✗ No configuration found for {provider}:{config_name}")
        ctx.exit(1)
        return

    with make_client(args.proxy, args.key) as c:
        # Get ticket details
        r = c.get(f"/dashboard/tickets/{ticket_id}")
        if r.status_code == 404:
            click.echo(f"  ✗ Ticket not found: {ticket_id}")
            ctx.exit(1)
            return
        ticket = r.json()

        # Get project details
        proj_id = ticket.get("project_id")
        if not proj_id:
            click.echo("  ✗ Ticket has no project")
            ctx.exit(1)
            return

        r = c.get(f"/dashboard/projects/{proj_id}")
        project = r.json()

        # Determine external project ID
        external_project = project_id
        if not external_project:
            link_key = f"{provider}_external_id"
            external_project = project.get(link_key)

        if not external_project:
            click.echo(f"  ✗ No linked {provider} project found")
            click.echo(f"  Link it first: proxym external project link {proj_id} {provider} <external_id>")
            ctx.exit(1)
            return

        # Prepare issue content
        title = ticket.get("title") or ticket.get("task", "")[:50]
        description_parts = []
        if ticket.get("task"):
            description_parts.append(f"**Task:** {ticket['task']}")
        if ticket.get("acceptance_criteria"):
            description_parts.append(f"\n**Acceptance Criteria:**\n{ticket['acceptance_criteria']}")
        description_parts.append(f"\n---\n*Synced from Proxym ticket {ticket_id}*")
        description = "\n".join(description_parts)

        label_list = [l.strip() for l in labels.split(",") if l.strip()]

        async def _create():
            provider_instance = get_provider(
                config.provider,
                config.api_key,
                config.base_url,
                config.email,
            )
            async with provider_instance:
                issue = await provider_instance.create_issue(
                    external_project, title, description, label_list
                )
                return issue

        try:
            issue = asyncio.run(_create())
        except Exception as e:
            click.echo(f"  ✗ Failed to sync issue: {e}")
            ctx.exit(1)
            return

        # Store sync reference in ticket
        c.put(f"/dashboard/tickets/{ticket_id}", json={
            f"{provider}_issue_url": issue.url,
            f"{provider}_issue_number": issue.number,
        })

    click.echo(f"  ✓ Synced ticket {ticket_id} to {provider}")
    click.echo(f"    Issue: #{issue.number} - {issue.title}")
    click.echo(f"    URL: {issue.url}")


# ── GitLab Shell integration ─────────────────────────────────

@external_group.group(name="gitlab-shell")
def external_gitlab_shell():
    """GitLab shell CLI integration commands."""
    pass


@external_gitlab_shell.command(name="exec")
@click.argument("project_id")
@click.argument("command")
@click.option("--config-name", default="default", help="Configuration name")
@click.pass_context
def gitlab_shell_exec(
    ctx: click.Context,
    project_id: str,
    command: str,
    config_name: str,
):
    """Execute a GitLab CLI command for a project.

    Requires the 'glab' CLI tool to be installed.
    Common commands: issues list, mr list, pipeline list, release list
    """
    import subprocess

    manager = get_config_manager()
    config = manager.get_provider("gitlab", config_name)

    if not config:
        click.echo(f"  ✗ No GitLab configuration found")
        ctx.exit(1)
        return

    # Set up environment with GitLab token
    env = dict(os.environ)
    env["GITLAB_TOKEN"] = config.api_key
    if config.base_url:
        env["GITLAB_URI"] = config.base_url.replace("/api/v4", "")

    # Build glab command
    cmd_parts = ["glab"] + command.split()

    try:
        result = subprocess.run(
            cmd_parts,
            env=env,
            capture_output=True,
            text=True,
            cwd=None,
        )

        if result.returncode == 0:
            click.echo(result.stdout)
        else:
            click.echo(f"  ✗ Command failed: {result.stderr}")
            ctx.exit(1)
    except FileNotFoundError:
        click.echo("  ✗ 'glab' CLI not found. Install it from:")
        click.echo("    https://gitlab.com/gitlab-org/cli#installation")
        ctx.exit(1)


@external_gitlab_shell.command(name="auth")
@click.option("--config-name", default="default", help="Configuration name")
@click.pass_context
def gitlab_shell_auth(ctx: click.Context, config_name: str):
    """Authenticate glab CLI with stored credentials."""
    import subprocess

    manager = get_config_manager()
    config = manager.get_provider("gitlab", config_name)

    if not config:
        click.echo(f"  ✗ No GitLab configuration found")
        ctx.exit(1)
        return

    # Build glab auth command
    hostname = config.base_url.replace("https://", "").replace("/api/v4", "") if config.base_url else "gitlab.com"

    cmd = [
        "glab", "auth", "login",
        "--hostname", hostname,
        "--token", config.api_key,
    ]

    try:
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
        )

        if result.returncode == 0:
            click.echo(f"  ✓ Authenticated glab for {hostname}")
        else:
            click.echo(f"  ✗ Authentication failed: {result.stderr}")
            ctx.exit(1)
    except FileNotFoundError:
        click.echo("  ✗ 'glab' CLI not found. Install it from:")
        click.echo("    https://gitlab.com/gitlab-org/cli#installation")
        ctx.exit(1)
