from __future__ import annotations

import argparse
import csv
import json
import os
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional

from autotouch_cli.exceptions import AutotouchInputError

__all__ = [
    "parse_json_string",
    "load_json_input",
    "load_required_object_payload",
    "normalize_email_value",
    "split_cli_email_values",
    "parse_email_payload",
    "load_emails_from_file",
    "dedupe_keep_order",
    "normalize_string_value",
    "split_cli_string_values",
    "parse_string_list_payload",
    "load_string_values_from_file",
    "chunk_list",
]


def parse_json_string(raw: str, context: str) -> Any:
    try:
        return json.loads(raw)
    except Exception as exc:
        raise AutotouchInputError(f"invalid JSON for {context}: {exc}") from exc


def load_json_input(
    *,
    inline_json: Optional[str],
    file_path: Optional[str],
    context: str,
    default: Any = None,
) -> Any:
    if inline_json and file_path:
        raise AutotouchInputError(f"pass either --{context}-json or --{context}-file, not both")
    if inline_json:
        return parse_json_string(inline_json, context)
    if file_path:
        try:
            if file_path == "-":
                return json.load(sys.stdin)
            with open(file_path, "r", encoding="utf-8") as handle:
                return json.load(handle)
        except Exception as exc:
            source = "stdin" if file_path == "-" else f"file '{file_path}'"
            raise AutotouchInputError(f"failed to read {context} {source}: {exc}") from exc
    return default


def load_required_object_payload(args: argparse.Namespace, *, noun: str) -> Dict[str, Any]:
    payload = load_json_input(
        inline_json=getattr(args, "data_json", None),
        file_path=getattr(args, "data_file", None),
        context="data",
        default=None,
    )
    if not isinstance(payload, dict):
        raise AutotouchInputError(f"{noun} requires --data-json/--data-file with a JSON object")
    return payload


def normalize_email_value(value: Any) -> Optional[str]:
    text = str(value or "").strip().lower()
    return text or None


def split_cli_email_values(raw_values: Optional[List[str]]) -> List[str]:
    emails: List[str] = []
    for raw in raw_values or []:
        for piece in str(raw).replace("\n", ",").split(","):
            normalized = normalize_email_value(piece)
            if normalized:
                emails.append(normalized)
    return emails


def parse_email_payload(payload: Any, *, context: str) -> List[str]:
    raw_values: List[Any]
    if isinstance(payload, list):
        raw_values = payload
    elif isinstance(payload, dict) and isinstance(payload.get("emails"), list):
        raw_values = payload.get("emails") or []
    else:
        raise AutotouchInputError(
            f"{context} must be a JSON array or an object with an 'emails' array"
        )

    emails: List[str] = []
    for item in raw_values:
        normalized = normalize_email_value(item)
        if normalized:
            emails.append(normalized)
    return emails


def load_emails_from_file(file_path: str, *, csv_column: Optional[str]) -> List[str]:
    resolved_path = os.path.abspath(os.path.expanduser(str(file_path)))
    suffix = Path(resolved_path).suffix.lower()

    try:
        if suffix == ".json":
            with open(resolved_path, "r", encoding="utf-8") as handle:
                payload = json.load(handle)
            return parse_email_payload(payload, context=f"emails file '{resolved_path}'")

        if suffix == ".csv":
            with open(resolved_path, "r", encoding="utf-8", newline="") as handle:
                reader = csv.DictReader(handle)
                rows = list(reader)
                fieldnames = [name for name in (reader.fieldnames or []) if name]

            if not fieldnames:
                return []

            preferred = str(csv_column or "").strip().lower()
            selected_column: Optional[str] = None
            if preferred:
                for field_name in fieldnames:
                    if str(field_name).strip().lower() == preferred:
                        selected_column = field_name
                        break

            if selected_column is None:
                normalized_fields = {
                    str(field_name).strip().lower(): field_name for field_name in fieldnames
                }
                for candidate in ("email", "work_email", "personal_email", "primary_email"):
                    if candidate in normalized_fields:
                        selected_column = normalized_fields[candidate]
                        break

            if selected_column is None:
                selected_column = fieldnames[0]

            emails: List[str] = []
            for row in rows:
                normalized = normalize_email_value((row or {}).get(selected_column))
                if normalized:
                    emails.append(normalized)
            return emails

        emails: List[str] = []
        with open(resolved_path, "r", encoding="utf-8") as handle:
            for line in handle:
                for piece in str(line).replace("\n", "").split(","):
                    normalized = normalize_email_value(piece)
                    if normalized:
                        emails.append(normalized)
        return emails

    except FileNotFoundError as exc:
        raise AutotouchInputError(f"emails file not found: {resolved_path}") from exc
    except Exception as exc:
        raise AutotouchInputError(f"failed to read emails file '{resolved_path}': {exc}") from exc


def dedupe_keep_order(values: List[str]) -> List[str]:
    seen: set[str] = set()
    deduped: List[str] = []
    for value in values:
        if value in seen:
            continue
        seen.add(value)
        deduped.append(value)
    return deduped


def normalize_string_value(value: Any) -> Optional[str]:
    text = str(value or "").strip()
    return text or None


def split_cli_string_values(raw_values: Optional[List[str]]) -> List[str]:
    values: List[str] = []
    for raw in raw_values or []:
        for piece in str(raw).replace("\n", ",").split(","):
            normalized = normalize_string_value(piece)
            if normalized:
                values.append(normalized)
    return values


def parse_string_list_payload(
    payload: Any,
    *,
    context: str,
    array_key: Optional[str] = None,
    key: Optional[str] = None,
) -> List[str]:
    raw_values: List[Any]
    selected_key = str(array_key or key or "").strip() or None
    if isinstance(payload, list):
        raw_values = payload
    elif isinstance(payload, dict) and selected_key and isinstance(payload.get(selected_key), list):
        raw_values = payload.get(selected_key) or []
    else:
        suffix = f" or an object with a '{selected_key}' array" if selected_key else ""
        raise AutotouchInputError(f"{context} must be a JSON array{suffix}")

    values: List[str] = []
    for item in raw_values:
        normalized = normalize_string_value(item)
        if normalized:
            values.append(normalized)
    return values


def load_string_values_from_file(
    file_path: str,
    *,
    context: str,
    array_key: str,
    csv_column: Optional[str] = None,
) -> List[str]:
    resolved_path = os.path.abspath(os.path.expanduser(str(file_path)))
    suffix = Path(resolved_path).suffix.lower()

    try:
        if suffix == ".json":
            with open(resolved_path, "r", encoding="utf-8") as handle:
                payload = json.load(handle)
            return parse_string_list_payload(payload, context=context, array_key=array_key)

        if suffix == ".csv":
            with open(resolved_path, "r", encoding="utf-8", newline="") as handle:
                reader = csv.DictReader(handle)
                rows = list(reader)
                fieldnames = [name for name in (reader.fieldnames or []) if name]

            if not fieldnames:
                return []

            preferred = str(csv_column or "").strip().lower()
            selected_column: Optional[str] = None
            if preferred:
                for field_name in fieldnames:
                    if str(field_name).strip().lower() == preferred:
                        selected_column = field_name
                        break

            if selected_column is None:
                normalized_fields = {
                    str(field_name).strip().lower(): field_name for field_name in fieldnames
                }
                fallback_candidates = (
                    preferred,
                    array_key.lower(),
                    "lead_id",
                    "leadid",
                    "id",
                )
                for candidate in fallback_candidates:
                    if candidate and candidate in normalized_fields:
                        selected_column = normalized_fields[candidate]
                        break

            if selected_column is None:
                selected_column = fieldnames[0]

            values: List[str] = []
            for row in rows:
                normalized = normalize_string_value((row or {}).get(selected_column))
                if normalized:
                    values.append(normalized)
            return values

        values: List[str] = []
        with open(resolved_path, "r", encoding="utf-8") as handle:
            for line in handle:
                for piece in str(line).replace("\n", "").split(","):
                    normalized = normalize_string_value(piece)
                    if normalized:
                        values.append(normalized)
        return values

    except FileNotFoundError as exc:
        raise AutotouchInputError(f"{context} file not found: {resolved_path}") from exc
    except Exception as exc:
        raise AutotouchInputError(f"failed to read {context} file '{resolved_path}': {exc}") from exc


def chunk_list(values: List[str], size: int) -> List[List[str]]:
    if size <= 0:
        return [values]
    return [values[idx : idx + size] for idx in range(0, len(values), size)]

