from __future__ import annotations

import argparse
import json
from dataclasses import dataclass
from typing import Any, Callable, Dict, Optional, Sequence

from autotouch_cli.exceptions import AutotouchInputError


@dataclass(frozen=True)
class LinkedInCommandRuntime:
    resolve_token: Callable[[Optional[str], bool], Optional[str]]
    request_api: Callable[..., Any]
    print_json: Callable[[Any, bool], None]
    load_json_input: Callable[..., Any]


def cmd_linkedin_status(args: argparse.Namespace, *, runtime: LinkedInCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    data = runtime.request_api(
        "GET",
        "/api/integrations/linkedin/status",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def cmd_linkedin_limits(args: argparse.Namespace, *, runtime: LinkedInCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    data = runtime.request_api(
        "GET",
        "/api/integrations/linkedin/limits",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def _normalize_linkedin_search_payload(args: argparse.Namespace, *, runtime: LinkedInCommandRuntime) -> Dict[str, Any]:
    explicit = runtime.load_json_input(
        inline_json=getattr(args, "data_json", None),
        file_path=getattr(args, "data_file", None),
        context="data",
        default=None,
    )
    if explicit is not None:
        if not isinstance(explicit, dict):
            raise AutotouchInputError("linkedin search payload must be a JSON object")
        return explicit

    payload: Dict[str, Any] = {
        "category": str(getattr(args, "category", "people") or "people"),
        "limit": max(1, min(int(getattr(args, "limit", 25) or 25), 250)),
    }
    url = str(getattr(args, "url", "") or "").strip()
    if url:
        payload["url"] = url
    keywords = str(getattr(args, "keywords", "") or "").strip()
    if keywords:
        payload["keywords"] = keywords
    linkedin_api = str(getattr(args, "linkedin_api", "classic") or "classic").strip()
    if linkedin_api:
        payload["linkedin_api"] = linkedin_api
    cursor = str(getattr(args, "cursor", "") or "").strip()
    if cursor:
        payload["cursor"] = cursor
    extra_json = str(getattr(args, "extra_params_json", "") or "").strip()
    if extra_json:
        payload["extra_params"] = json.loads(extra_json)
    return payload


def cmd_linkedin_search(args: argparse.Namespace, *, runtime: LinkedInCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    payload = _normalize_linkedin_search_payload(args, runtime=runtime)
    data = runtime.request_api(
        "POST",
        "/api/integrations/linkedin/search",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        payload=payload,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def cmd_linkedin_search_params(args: argparse.Namespace, *, runtime: LinkedInCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    payload: Dict[str, Any] = {
        "type": str(args.type).strip(),
        "keywords": str(getattr(args, "keywords", "") or "").strip(),
        "limit": max(1, min(int(getattr(args, "limit", 25) or 25), 250)),
    }
    data = runtime.request_api(
        "POST",
        "/api/integrations/linkedin/search/parameters",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        payload=payload,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def cmd_linkedin_recipe(
    args: argparse.Namespace,
    *,
    runtime: LinkedInCommandRuntime,
    recipes: Dict[str, Any],
) -> None:
    selected = str(args.type or "all")
    if selected == "all":
        output = {"recipes": recipes}
        if args.out_file:
            with open(args.out_file, "w", encoding="utf-8") as f:
                json.dump(output, f, indent=2)
        runtime.print_json(output, args.compact)
        return

    recipe = recipes.get(selected)
    if not recipe:
        raise AutotouchInputError(f"unknown recipe type '{selected}'")
    output = {"type": selected, **recipe}
    if args.out_file and recipe.get("payload") is not None:
        with open(args.out_file, "w", encoding="utf-8") as f:
            json.dump(recipe["payload"], f, indent=2)
    runtime.print_json(output, args.compact)


def cmd_linkedin_posts(args: argparse.Namespace, *, runtime: LinkedInCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    identifier = str(args.identifier).strip()
    params: Dict[str, Any] = {
        "limit": max(1, min(int(getattr(args, "limit", 25) or 25), 100)),
    }
    cursor = str(getattr(args, "cursor", "") or "").strip()
    if cursor:
        params["cursor"] = cursor

    data = runtime.request_api(
        "GET",
        f"/api/integrations/linkedin/posts/{identifier}",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        params=params,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def cmd_linkedin_post(args: argparse.Namespace, *, runtime: LinkedInCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    post_id = str(args.post_id).strip()
    data = runtime.request_api(
        "GET",
        f"/api/integrations/linkedin/posts/{post_id}/detail",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def cmd_linkedin_comments(args: argparse.Namespace, *, runtime: LinkedInCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    post_id = str(args.post_id).strip()
    params: Dict[str, Any] = {
        "limit": max(1, min(int(getattr(args, "limit", 100) or 100), 100)),
    }
    cursor = str(getattr(args, "cursor", "") or "").strip()
    if cursor:
        params["cursor"] = cursor

    data = runtime.request_api(
        "GET",
        f"/api/integrations/linkedin/posts/{post_id}/comments",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        params=params,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def cmd_linkedin_reactions(args: argparse.Namespace, *, runtime: LinkedInCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    post_id = str(args.post_id).strip()
    params: Dict[str, Any] = {
        "limit": max(1, min(int(getattr(args, "limit", 100) or 100), 100)),
    }
    cursor = str(getattr(args, "cursor", "") or "").strip()
    if cursor:
        params["cursor"] = cursor

    data = runtime.request_api(
        "GET",
        f"/api/integrations/linkedin/posts/{post_id}/reactions",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        params=params,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def register_linkedin_subcommands(
    subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
    *,
    add_api_common_arguments: Callable[[argparse.ArgumentParser], None],
    handlers: Dict[str, Callable[[argparse.Namespace], None]],
    recipe_types: Sequence[str],
) -> None:
    parser = subparsers.add_parser("linkedin", help="LinkedIn list-building operations")
    linkedin_sub = parser.add_subparsers(dest="linkedin_cmd", required=True)

    recipe_parser = linkedin_sub.add_parser("recipe", help="Print LinkedIn payload recipes for search and list-building")
    recipe_parser.add_argument("--type", choices=["all", *recipe_types], default="all")
    recipe_parser.add_argument("--out-file", help="Optional path to save the selected payload JSON")
    add_api_common_arguments(recipe_parser)
    recipe_parser.set_defaults(func=handlers["recipe"])

    status_parser = linkedin_sub.add_parser("status", help="Check LinkedIn connection status")
    add_api_common_arguments(status_parser)
    status_parser.set_defaults(func=handlers["status"])

    limits_parser = linkedin_sub.add_parser("limits", help="View LinkedIn send limits and usage")
    add_api_common_arguments(limits_parser)
    limits_parser.set_defaults(func=handlers["limits"])

    search_parser = linkedin_sub.add_parser("search", help="Search LinkedIn for people, companies, jobs, or posts")
    search_parser.add_argument("--data-json", help="Explicit search payload JSON (overrides individual flags)")
    search_parser.add_argument("--data-file", help="Path to search payload JSON file")
    search_parser.add_argument("--url", help="Full LinkedIn/Sales Navigator search URL (overrides keyword filters)")
    search_parser.add_argument("--category", choices=["people", "companies", "jobs", "posts"], default="people", help="Search category (default: people)")
    search_parser.add_argument("--keywords", help="Free-text keyword search")
    search_parser.add_argument("--linkedin-api", choices=["classic", "sales_navigator"], default="classic", help="API mode (default: classic)")
    search_parser.add_argument("--cursor", help="Pagination cursor from previous response")
    search_parser.add_argument("--limit", type=int, default=25, help="Results per page, 1-250 (default: 25)")
    search_parser.add_argument("--extra-params-json", help="JSON object with additional search filters (location IDs, industry, etc.)")
    add_api_common_arguments(search_parser)
    search_parser.set_defaults(func=handlers["search"])

    params_parser = linkedin_sub.add_parser("search-params", help="Resolve names to LinkedIn parameter IDs for search filters")
    params_parser.add_argument("--type", required=True, help="Parameter type: location, industry, company, school, title, language, etc.")
    params_parser.add_argument("--keywords", default="", help="Search text to match")
    params_parser.add_argument("--limit", type=int, default=25, help="Max results, 1-250 (default: 25)")
    add_api_common_arguments(params_parser)
    params_parser.set_defaults(func=handlers["search_params"])

    posts_parser = linkedin_sub.add_parser("posts", help="List LinkedIn posts for a user")
    posts_parser.add_argument("--identifier", required=True, help="LinkedIn profile URL, public ID, or URN")
    posts_parser.add_argument("--cursor", help="Pagination cursor from previous response")
    posts_parser.add_argument("--limit", type=int, default=25, help="Results per page, 1-100 (default: 25)")
    add_api_common_arguments(posts_parser)
    posts_parser.set_defaults(func=handlers["posts"])

    post_parser = linkedin_sub.add_parser("post", help="Get full details for a single LinkedIn post")
    post_parser.add_argument("--post-id", required=True, help="LinkedIn post ID")
    add_api_common_arguments(post_parser)
    post_parser.set_defaults(func=handlers["post"])

    comments_parser = linkedin_sub.add_parser("comments", help="List comments on a LinkedIn post")
    comments_parser.add_argument("--post-id", required=True, help="LinkedIn post ID")
    comments_parser.add_argument("--cursor", help="Pagination cursor from previous response")
    comments_parser.add_argument("--limit", type=int, default=100, help="Results per page, 1-100 (default: 100)")
    add_api_common_arguments(comments_parser)
    comments_parser.set_defaults(func=handlers["comments"])

    reactions_parser = linkedin_sub.add_parser("reactions", help="List reactions on a LinkedIn post")
    reactions_parser.add_argument("--post-id", required=True, help="LinkedIn post ID")
    reactions_parser.add_argument("--cursor", help="Pagination cursor from previous response")
    reactions_parser.add_argument("--limit", type=int, default=100, help="Results per page, 1-100 (default: 100)")
    add_api_common_arguments(reactions_parser)
    reactions_parser.set_defaults(func=handlers["reactions"])
