#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Manual email service - ETKİLEŞİMLİ (soru-cevap) konsol aracı.

Özellikler:
- Parametresiz çalışır, menü ile ilerler.
- config.json varsa API anahtarını ve TLS/Proxy CA ayarlarını okur.
- 'order' (mail satın al) ve 'wait' (kod bekle) akışları.
- TLS teşhis menüsü.
- Hatalarda anlaşılır mesajlar; işlem sonunda menüye dönüş.
"""

from __future__ import annotations

import json
import os
import ssl
import sys
import time
from getpass import getpass
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple

from anymessage import EmailOrderResult, MailCheckResult, order_email, wait_for_email
from email_utils import is_gmail_address, remove_dots_from_local_part

BASE_DIR = Path(__file__).resolve().parent
DEFAULT_CONFIG_FILE = BASE_DIR / "config.json"
EMAIL_REREG_LOG_FILE = BASE_DIR / "Email_information_awaiting_re-registration.txt"
EMAIL_REREG_OFFSET_SECONDS = 40 * 60
EMAIL_REREG_DEFAULT_SERVICE = "anymessage"


# ────────────────────────────────────────────────────────────
# Config & TLS Yardımcıları
# ────────────────────────────────────────────────────────────

def _load_config(config_path: Path) -> dict:
    try:
        return json.loads(config_path.read_text(encoding="utf-8"))
    except FileNotFoundError:
        return {}
    except json.JSONDecodeError:
        return {}

def _load_api_key_from_config(config: dict) -> Optional[str]:
    key = config.get("AnyMessage")
    if isinstance(key, str) and key.strip():
        return key.strip()
    return None

def _apply_tls_env_from_config(config: dict) -> None:
    """
    Eğer config'te ca_bundle_pem verilmişse ve dosya mevcutsa,
    Requests/urllib/curl ekosistemi için uygun ortam değişkenlerini ayarlar.
    """
    ca_path = config.get("ca_bundle_pem")
    set_env = bool(config.get("set_requests_ca_bundle_env", True))
    if not set_env:
        return
    if isinstance(ca_path, str) and ca_path.strip():
        p = Path(ca_path).expanduser()
        if p.is_file():
            os.environ["REQUESTS_CA_BUNDLE"] = str(p)
            os.environ["SSL_CERT_FILE"] = str(p)
            os.environ["CURL_CA_BUNDLE"] = str(p)
        # değilse sessiz geç; teşhis menüsünde kullanıcıyı bilgilendiririz

def _print_tls_diagnostics(config: dict) -> None:
    print("\n==== TLS Teşhis ====")
    print(f"Config dosyası        : {DEFAULT_CONFIG_FILE}")
    ca_in_cfg = config.get("ca_bundle_pem")
    print(f"config.ca_bundle_pem  : {ca_in_cfg!r}")
    for var in ("REQUESTS_CA_BUNDLE", "SSL_CERT_FILE", "CURL_CA_BUNDLE"):
        print(f"env {var:<17}: {os.environ.get(var) or '-'}")

    vp = ssl.get_default_verify_paths()
    print("\nPython ssl verify paths (read-only):")
    print(f"  cafile      : {vp.cafile or '-'}")
    print(f"  capath      : {vp.capath or '-'}")
    print(f"  openssl_cafile_env : {vp.openssl_cafile_env or '-'}")
    print(f"  openssl_capath_env : {vp.openssl_capath_env or '-'}")
    print("\nYorum:")
    print("- Eğer bir MITM proxy (mitmproxy/Burp/Charles/AV) kullanıyorsanız,"
          " onun CA sertifikasını .pem olarak dışa aktarın ve config.json'da"
          " 'ca_bundle_pem' alanına dosya yolunu yazın.")
    print("- Kurumsal ortamdaysanız, işletim sistemi kök deposuna CA'nın yüklü olduğundan"
          " ve Python/Requests'in bunu görebildiğinden emin olun.")
    _press_enter()

# ────────────────────────────────────────────────────────────
# Basit IO Yardımcıları
# ────────────────────────────────────────────────────────────

def _ensure_api_key_interactive(config: dict) -> str:
    """
    API anahtarını config.json'dan okumaya çalışır; yoksa kullanıcıdan ister.
    getpass ile gizli girdi alınır.
    """
    key = _load_api_key_from_config(config)
    if key:
        return key
    print("\n[Bilgi] config.json bulunamadı ya da 'AnyMessage' yok.")
    while True:
        k = getpass("Mail servis API anahtarını girin: ").strip()
        if k:
            return k
        print("Hata: boş bırakılamaz.")

def _ask(prompt: str, default: Optional[str] = None, allow_empty: bool = False) -> str:
    suffix = f" [{default}]" if default is not None else ""
    while True:
        val = input(f"{prompt}{suffix}: ").strip()
        if val:
            return val
        if not val and default is not None:
            return default
        if allow_empty:
            return ""
        print("Lütfen bir değer girin.")

def _ask_int(prompt: str, default: Optional[int] = None, min_value: Optional[int] = None) -> int:
    while True:
        raw = _ask(prompt, str(default) if default is not None else None)
        try:
            val = int(raw)
            if min_value is not None and val < min_value:
                print(f"En az {min_value} olmalı.")
                continue
            return val
        except ValueError:
            print("Lütfen sayı girin.")

def _yes_no(prompt: str, default_yes: bool = True) -> bool:
    d = "Y/n" if default_yes else "y/N"
    while True:
        ans = input(f"{prompt} ({d}): ").strip().lower()
        if not ans:
            return default_yes
        if ans in ("y", "yes", "evet", "e"):
            return True
        if ans in ("n", "no", "hayır", "h"):
            return False
        print("Lütfen 'y' veya 'n' girin.")

def _print_order_result(res: EmailOrderResult, verbose: bool = False) -> None:
    print("\n==== Mail Satın Alma Sonucu ====")
    print(f"Durum       : {'BAŞARILI' if res.ok else 'BAŞARISIZ'}")
    print(f"HTTP Status : {res.status_code}")
    if res.error and not res.ok:
        print(f"Hata        : {res.error}")
        _hint_on_ssl_error(str(res.error))
    if verbose:
        raw = getattr(res, "text", None)
        if isinstance(raw, str):
            print(f"Ham Yanıt   : {raw[:500]}")
        if isinstance(getattr(res, "data", None), dict):
            print("JSON Yanıtı :")
            for k, v in res.data.items():
                print(f"  - {k}: {v}")

def _print_wait_result(res: MailCheckResult, verbose: bool = False) -> None:
    print("\n==== Mail Kontrol Sonucu ====")
    print(f"Durum        : {'BAŞARILI' if res.ok else 'BAŞARISIZ'}")
    if getattr(res, "code", None):
        print(f"Doğrulama Kodu: {res.code}")
    print(f"HTTP Status  : {res.status_code}")
    print(f"Deneme Sayısı: {res.attempts}")
    if res.error and not res.ok:
        print(f"Hata         : {res.error}")
        _hint_on_ssl_error(str(res.error))
    if verbose:
        raw = getattr(res, "text", None)
        if isinstance(raw, str) and raw:
            print(f"Ham Yanıt    : {raw[:500]}")
        if isinstance(getattr(res, "data", None), dict):
            print("JSON Yanıtı  :")
            for k, v in res.data.items():
                print(f"  - {k}: {v}")

def _press_enter():
    input("\nDevam etmek için Enter'a basın...")

def _hint_on_ssl_error(msg: str) -> None:
    low = (msg or "").lower()
    if "certificate verify failed" in low or "self-signed certificate" in low:
        print("\n[İpucu] Sertifika doğrulama hatası tespit edildi.")
        print(" - Büyük olasılıkla bir TLS kesici (proxy/AV) veya eksik CA kökü var.")
        print(" - Çözüm: Proxy/AV kök sertifikanızı .pem olarak dışa aktarın ve")
        print("   config.json içine 'ca_bundle_pem' alanı ile yolunu yazın;")
        print("   'set_requests_ca_bundle_env': true olduğundan emin olun.")
        print(" - Alternatif: Kök CA'yı sistemin güvenilir deposuna kalıcı eklemek.")

# ────────────────────────────────────────────────────────────
# Kayıt yardımcıları
# ────────────────────────────────────────────────────────────


def _safe_int(value: Any, default: int = 0) -> int:
    try:
        return int(value)
    except (TypeError, ValueError):
        return default


def _iter_payload_dicts(data: Dict[str, Any]) -> Iterable[Dict[str, Any]]:
    stack = [data]
    seen: Set[int] = set()
    while stack:
        current = stack.pop()
        if not isinstance(current, dict):
            continue
        identity = id(current)
        if identity in seen:
            continue
        seen.add(identity)
        yield current
        for value in current.values():
            if isinstance(value, dict):
                stack.append(value)


def _extract_text_field(data: Dict[str, Any], keys: Iterable[str]) -> str:
    for payload in _iter_payload_dicts(data):
        for key in keys:
            if key not in payload:
                continue
            value = payload.get(key)
            if value is None:
                continue
            if isinstance(value, str):
                text = value.strip()
            else:
                text = str(value).strip()
            if text:
                return text
    return ""


def _extract_int_field(data: Dict[str, Any], keys: Iterable[str]) -> Optional[int]:
    for payload in _iter_payload_dicts(data):
        for key in keys:
            if key not in payload:
                continue
            value = payload.get(key)
            try:
                return int(value)
            except (TypeError, ValueError):
                continue
    return None


def _extract_order_identifiers(payload: Dict[str, Any]) -> Tuple[str, str]:
    email_id = _extract_text_field(
        payload,
        ("email_id", "id", "order_id", "emailId", "emailID"),
    ).strip()
    email_address = _extract_text_field(
        payload,
        ("email_address", "email", "address", "mail"),
    ).strip()
    return email_id, email_address


def _build_rereg_entry(order_data: Dict[str, Any], api_key: str) -> Optional[Dict[str, Any]]:
    email_id, email_address = _extract_order_identifiers(order_data)

    storage_address = remove_dots_from_local_part(email_address)
    if storage_address:
        email_address = storage_address

    if not email_id and not email_address:
        return None

    service = _extract_text_field(order_data, ("service", "provider"))
    service = service.strip().lower() or EMAIL_REREG_DEFAULT_SERVICE

    verification_code = _extract_text_field(order_data, ("verification_code", "code"))
    verification_code = verification_code.strip()

    count_value = _extract_int_field(order_data, ("count", "uses", "usage_count"))
    if count_value is None:
        count_value = 0
    else:
        count_value = max(0, count_value)

    last_used = max(0, int(time.time()) - EMAIL_REREG_OFFSET_SECONDS)

    entry: Dict[str, Any] = {
        "email_id": email_id,
        "email_address": email_address,
        "service": service,
        "api_key": api_key.strip(),
        "verification_code": verification_code,
        "count": count_value,
        "last_used": last_used,
    }

    if not entry["verification_code"]:
        entry["verification_code"] = ""

    return entry


def _persist_rereg_entry(entry: Dict[str, Any]) -> bool:
    email_id = str(entry.get("email_id") or "").strip()
    email_address = str(entry.get("email_address") or "").strip()

    normalized_for_storage = remove_dots_from_local_part(email_address)
    if normalized_for_storage:
        email_address = normalized_for_storage
        entry["email_address"] = normalized_for_storage

    non_gmail_address = False
    if email_address and not is_gmail_address(email_address):
        non_gmail_address = True
        print(
            "[Uyarı] Gmail olmayan bir adres tespit edildi; yine de kayıt Email_information_awaiting_re-registration.txt dosyasına eklenecek."
        )

    existing_entries: List[Dict[str, Any]] = []
    raw_lines: List[str] = []
    existing_count: Optional[int] = None
    existing_verification: Optional[str] = None

    if EMAIL_REREG_LOG_FILE.exists():
        try:
            with EMAIL_REREG_LOG_FILE.open("r", encoding="utf-8") as handle:
                for line in handle:
                    stripped = line.strip()
                    if not stripped:
                        continue
                    try:
                        payload = json.loads(stripped)
                    except json.JSONDecodeError:
                        raw_lines.append(stripped)
                        continue
                    if not isinstance(payload, dict):
                        raw_lines.append(stripped)
                        continue

                    current_id = str(payload.get("email_id") or "").strip()
                    current_address = str(payload.get("email_address") or "").strip()

                    matches = False
                    if email_id and current_id == email_id:
                        matches = True
                    elif not email_id and email_address and current_address == email_address:
                        matches = True

                    if matches:
                        if existing_count is None:
                            try:
                                existing_count = int(payload.get("count", 0))
                            except (TypeError, ValueError):
                                existing_count = None
                        if existing_verification is None:
                            value = payload.get("verification_code")
                            if isinstance(value, str):
                                existing_verification = value.strip() or None
                        continue

                    existing_entries.append(payload)
        except OSError as exc:
            print(
                f"[Uyarı] Email_information_awaiting_re-registration.txt okunamadı: {exc}"
            )
            return False

    if existing_count is not None:
        entry["count"] = max(0, existing_count)
    else:
        entry["count"] = max(0, _safe_int(entry.get("count"), 0))

    verification_text = str(entry.get("verification_code") or "").strip()
    if not verification_text and existing_verification:
        verification_text = existing_verification
    entry["verification_code"] = verification_text

    entry["service"] = str(entry.get("service") or "").strip().lower() or EMAIL_REREG_DEFAULT_SERVICE
    entry["api_key"] = str(entry.get("api_key") or "").strip()
    entry["email_id"] = email_id
    entry["email_address"] = email_address
    entry["last_used"] = max(0, _safe_int(entry.get("last_used"), 0))

    try:
        EMAIL_REREG_LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
    except OSError:
        pass

    entries_to_write = existing_entries + [entry]

    try:
        with EMAIL_REREG_LOG_FILE.open("w", encoding="utf-8") as handle:
            for raw in raw_lines:
                handle.write(raw + "\n")
            for payload in entries_to_write:
                handle.write(json.dumps(payload, ensure_ascii=False) + "\n")
    except OSError as exc:
        print(
            f"[Uyarı] Email_information_awaiting_re-registration.txt yazılamadı: {exc}"
        )
        return False

    identifier = email_id or email_address or "?"
    if non_gmail_address:
        print(
            f"[Bilgi] Gmail dışındaki adres başarıyla kaydedildi ({identifier})."
        )
    else:
        print(
            f"[Bilgi] Sipariş kaydı Email_information_awaiting_re-registration.txt dosyasına işlendi ({identifier})."
        )
    return True


def _record_order_for_rereg(res: EmailOrderResult, api_key: str) -> Optional[Dict[str, Any]]:
    payload = res.data if isinstance(res.data, dict) else None
    if not payload:
        print(
            "[Uyarı] Sipariş yanıtı beklenen formatta olmadığından Email_information_awaiting_re-registration.txt güncellenemedi."
        )
        return None

    entry = _build_rereg_entry(payload, api_key)
    if not entry:
        print(
            "[Uyarı] Sipariş yanıtında email_id veya email_address bulunamadı; kayıt yapılmadı."
        )
        return None

    persisted = _persist_rereg_entry(entry)
    if not persisted:
        return None
    return entry


# ────────────────────────────────────────────────────────────
# İşlemler
# ────────────────────────────────────────────────────────────


def flow_order(config: dict, verbose: bool = False) -> None:
    print("\n— Yeni E-posta Siparişi —")
    api_key = _ensure_api_key_interactive(config)
    try:
        res = order_email(api_key)
    except Exception as e:
        print(f"Hata: order_email sırasında istisna: {e.__class__.__name__}: {e}")
        _hint_on_ssl_error(str(e))
        _press_enter()
        return
    _print_order_result(res, verbose=verbose)

    # JSON yanıtında id varsa gösterelim
    if isinstance(getattr(res, "data", None), dict):
        email_id, email_addr = _extract_order_identifiers(res.data)
        if email_id:
            print(f"\nSipariş ID  : {email_id}")
        if email_addr:
            print(f"E-posta     : {email_addr}")

    if res.ok:
        _record_order_for_rereg(res, api_key)

    _press_enter()


def flow_bulk_order(config: dict, verbose: bool = False) -> None:
    print("\n— Toplu E-posta Siparişi —")
    api_key = _ensure_api_key_interactive(config)
    total_orders = _ask_int("Kaç adet e-posta alınsın?", default=1, min_value=1)

    successes = 0
    failures = 0
    saved_entries: List[Dict[str, Any]] = []

    for index in range(1, total_orders + 1):
        print(f"\n[{index}/{total_orders}] Sipariş gönderiliyor...")
        try:
            res = order_email(api_key)
        except Exception as exc:
            print(f"Hata: order_email sırasında istisna: {exc.__class__.__name__}: {exc}")
            _hint_on_ssl_error(str(exc))
            failures += 1
            continue

        _print_order_result(res, verbose=verbose)

        payload = res.data if isinstance(res.data, dict) else None
        email_id = ""
        email_address = ""
        if payload:
            email_id, email_address = _extract_order_identifiers(payload)

        if res.ok:
            successes += 1
            entry = _record_order_for_rereg(res, api_key)
            if entry:
                saved_entries.append(entry)
            elif not (email_id or email_address):
                print(
                    "[Uyarı] Sipariş yanıtında email_id veya email_address bulunamadığı için kayıt dosyasına yazılamadı."
                )
        else:
            failures += 1

    print("\n[Özet] Başarılı sipariş sayısı:", successes)
    print("[Özet] Başarısız sipariş sayısı:", failures)
    if saved_entries:
        print("[Özet] Kayıt edilen siparişler:")
        for idx, entry in enumerate(saved_entries, start=1):
            email_id = entry.get("email_id") or "-"
            email_address = entry.get("email_address") or "-"
            print(f"  {idx}) ID: {email_id}  E-posta: {email_address}")
        print(f"[Özet] Kayıt dosyası: {EMAIL_REREG_LOG_FILE}")

    _press_enter()

def flow_wait(config: dict, verbose: bool = False) -> None:
    print("\n— Kod Bekleme —")
    api_key = _ensure_api_key_interactive(config)

    email_id = _ask("Sipariş (EMAIL_ID)")
    timeout = _ask_int("Kod bekleme süresi (sn)", default=180, min_value=1)
    interval = _ask_int("Kontrol aralığı (sn)", default=2, min_value=1)
    reject_code = _ask("Bu kod gelirse sonlandır (opsiyonel, boş bırak)", default="", allow_empty=True)

    print("\nBekleme başlatılıyor...")
    try:
        res = wait_for_email(
            api_key=api_key,
            email_id=email_id,
            timeout_sec=timeout,
            interval_sec=interval,
            verbose=verbose,
            reject_code=(reject_code or None),
        )
    except Exception as e:
        print(f"Hata: wait_for_email sırasında istisna: {e.__class__.__name__}: {e}")
        _hint_on_ssl_error(str(e))
        _press_enter()
        return

    _print_wait_result(res, verbose=verbose)
    _press_enter()

# ────────────────────────────────────────────────────────────
# Ana menü
# ────────────────────────────────────────────────────────────

def main() -> None:
    print("Manual Email Service (interaktif)")
    print("Config dosyası: ", DEFAULT_CONFIG_FILE)
    config = _load_config(DEFAULT_CONFIG_FILE)
    _apply_tls_env_from_config(config)
    verbose = _yes_no("Ayrıntılı (verbose) çıktı açılsın mı?", default_yes=False)

    while True:
        print("\n=== MENÜ ===")
        print("1) Yeni e-posta sipariş et (order)")
        print("2) Kod bekle (wait)")
        print("3) Toplu e-posta siparişi (bulk order)")
        print("0) TLS teşhis (sertifika yollarını göster)")
        print("4) Çıkış")

        choice = _ask("Seçim yapın", default="1")
        if choice == "1":
            flow_order(config, verbose=verbose)
        elif choice == "2":
            flow_wait(config, verbose=verbose)
        elif choice == "3":
            flow_bulk_order(config, verbose=verbose)
        elif choice == "0":
            _print_tls_diagnostics(config)
        elif choice == "4":
            print("Güle güle 👋")
            return
        else:
            print("Geçersiz seçim.")


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\nİptal edildi.")
        sys.exit(130)
