"""UTM URL Builder CLI.

Usage examples:
    python3 utm_builder.py --source meta --medium cpc --campaign AB_A_snu \\
        --content carousel_a1 --base https://incar-top.tistory.com

    python3 utm_builder.py --batch items.json
"""

import argparse
import json
import sys
from typing import Optional, TypedDict
from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse

# ---------------------------------------------------------------------------
# Allowed values
# ---------------------------------------------------------------------------
ALLOWED_SOURCES: list[str] = [
    "meta",
    "google",
    "naver_sa",
    "naver_gfa",
    "kakao",
    "danggeun",
    "saramin",
    "jobkorea",
]

ALLOWED_MEDIUMS: list[str] = [
    "cpc",
    "display",
    "social",
    "job_board",
]

ALLOWED_CAMPAIGNS: list[str] = [
    "AB_A_snu",
    "AB_B_incar",
    "org_move",
    "always_A",
    "always_B",
    "urgent_A",
]

ALLOWED_LANDING_DOMAINS: list[str] = [
    "incar-top.tistory.com",
    "incar-top1.tistory.com",
]


# ---------------------------------------------------------------------------
# Types
# ---------------------------------------------------------------------------
class ValidationError(ValueError):
    """Raised when UTM parameter validation fails."""


class BatchItem(TypedDict, total=False):
    """Single item in a batch JSON file."""

    source: str
    medium: str
    campaign: str
    content: str
    term: str
    base: str


class BatchResult(TypedDict):
    """Result for a single batch item."""

    index: int
    url: Optional[str]
    error: Optional[str]


# ---------------------------------------------------------------------------
# Core logic
# ---------------------------------------------------------------------------
def validate_params(
    source: str,
    medium: str,
    campaign: str,
    base: str,
    content: Optional[str] = None,
    term: Optional[str] = None,
) -> None:
    """Validate UTM parameters against allowed values.

    Raises:
        ValidationError: if any parameter is invalid.
    """
    errors: list[str] = []

    if source not in ALLOWED_SOURCES:
        errors.append(f"Invalid utm_source '{source}'. " f"Allowed values: {', '.join(ALLOWED_SOURCES)}")

    if medium not in ALLOWED_MEDIUMS:
        errors.append(f"Invalid utm_medium '{medium}'. " f"Allowed values: {', '.join(ALLOWED_MEDIUMS)}")

    if campaign not in ALLOWED_CAMPAIGNS:
        errors.append(f"Invalid utm_campaign '{campaign}'. " f"Allowed values: {', '.join(ALLOWED_CAMPAIGNS)}")

    parsed: ParseResult = urlparse(base)
    if not parsed.scheme or not parsed.netloc:
        errors.append(f"Invalid base URL '{base}'. " f"Allowed landing domains: {', '.join(ALLOWED_LANDING_DOMAINS)}")
    else:
        domain = parsed.netloc.split(":")[0]  # strip port if any
        if domain not in ALLOWED_LANDING_DOMAINS:
            errors.append(
                f"Invalid landing page domain '{domain}'. " f"Allowed domains: {', '.join(ALLOWED_LANDING_DOMAINS)}"
            )

    if errors:
        raise ValidationError("\n".join(errors))


def build_utm_url(
    base: str,
    source: str,
    medium: str,
    campaign: str,
    content: Optional[str] = None,
    term: Optional[str] = None,
) -> str:
    """Build a UTM-tagged URL from parameters.

    Assumes parameters have already been validated.
    """
    parsed = urlparse(base)

    utm_params: dict[str, str] = {
        "utm_source": source,
        "utm_medium": medium,
        "utm_campaign": campaign,
    }
    if content:
        utm_params["utm_content"] = content
    if term:
        utm_params["utm_term"] = term

    # Preserve existing query string parameters
    existing_qs = parse_qs(parsed.query, keep_blank_values=True)
    # Flatten existing query params (first value only)
    flat_existing: dict[str, str] = {k: v[0] for k, v in existing_qs.items()}

    # UTM params take precedence over existing params with same key
    merged = {**flat_existing, **utm_params}

    new_query = urlencode(merged)
    new_parsed = parsed._replace(query=new_query)
    return urlunparse(new_parsed)


def process_batch(items: list[BatchItem]) -> list[BatchResult]:
    """Process a list of batch items, returning results for each.

    Validation errors produce BatchResult with url=None, error=<message>.
    """
    results: list[BatchResult] = []
    for idx, item in enumerate(items):
        source = item.get("source", "")
        medium = item.get("medium", "")
        campaign = item.get("campaign", "")
        content = item.get("content")
        term = item.get("term")
        base = item.get("base", "")

        try:
            validate_params(
                source=source,
                medium=medium,
                campaign=campaign,
                base=base,
                content=content,
                term=term,
            )
            url = build_utm_url(
                base=base,
                source=source,
                medium=medium,
                campaign=campaign,
                content=content,
                term=term,
            )
            results.append({"index": idx, "url": url, "error": None})
        except ValidationError as exc:
            results.append({"index": idx, "url": None, "error": str(exc)})

    return results


# ---------------------------------------------------------------------------
# Clipboard helper (optional)
# ---------------------------------------------------------------------------
def _try_copy_to_clipboard(text: str) -> bool:
    """Try to copy text to clipboard via pyperclip. Returns True on success."""
    try:
        import pyperclip  # type: ignore[import-untyped]

        pyperclip.copy(text)
        return True
    except ImportError:
        return False
    except Exception:
        return False


# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------
def _build_arg_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description="Build UTM-tagged URLs following team naming standards.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    parser.add_argument("--source", help="UTM source (required unless --batch)")
    parser.add_argument("--medium", help="UTM medium (required unless --batch)")
    parser.add_argument("--campaign", help="UTM campaign (required unless --batch)")
    parser.add_argument("--content", default=None, help="UTM content (optional)")
    parser.add_argument("--term", default=None, help="UTM term (optional, for search ads)")
    parser.add_argument("--base", help="Base landing page URL (required unless --batch)")
    parser.add_argument(
        "--batch",
        metavar="JSON_FILE",
        help="Path to a JSON file containing a list of UTM parameter sets",
    )
    return parser


def _run_single(args: argparse.Namespace) -> int:
    """Handle single URL generation. Returns exit code."""
    missing = [f for f in ("source", "medium", "campaign", "base") if not getattr(args, f)]
    if missing:
        print(
            f"Error: --{missing[0]} is required when not using --batch",
            file=sys.stderr,
        )
        return 1

    try:
        validate_params(
            source=args.source,
            medium=args.medium,
            campaign=args.campaign,
            base=args.base,
            content=args.content,
            term=args.term,
        )
    except ValidationError as exc:
        print(f"Validation error:\n{exc}", file=sys.stderr)
        return 1

    url = build_utm_url(
        base=args.base,
        source=args.source,
        medium=args.medium,
        campaign=args.campaign,
        content=args.content,
        term=args.term,
    )
    print(url)

    if _try_copy_to_clipboard(url):
        print("(Copied to clipboard)", file=sys.stderr)

    return 0


def _run_batch(json_path: str) -> int:
    """Handle batch URL generation from JSON file. Returns exit code."""
    try:
        with open(json_path, encoding="utf-8") as f:
            raw = json.load(f)
    except FileNotFoundError:
        print(f"Error: batch file not found: {json_path}", file=sys.stderr)
        return 1
    except json.JSONDecodeError as exc:
        print(f"Error: invalid JSON in batch file: {exc}", file=sys.stderr)
        return 1

    if not isinstance(raw, list):
        print("Error: batch JSON must be an array of objects.", file=sys.stderr)
        return 1

    items: list[BatchItem] = raw
    results = process_batch(items)

    has_error = False
    for result in results:
        idx = result["index"]
        if result["error"] is not None:
            print(f"[{idx + 1}] ERROR: {result['error']}", file=sys.stderr)
            has_error = True
        else:
            print(f"[{idx + 1}] {result['url']}")

    return 1 if has_error else 0


def main() -> None:
    parser = _build_arg_parser()
    args = parser.parse_args()

    if args.batch:
        exit_code = _run_batch(args.batch)
    else:
        exit_code = _run_single(args)

    sys.exit(exit_code)


if __name__ == "__main__":
    main()
