import argparse
import json
import os
import time
import pathlib
import requests
import logging
import webbrowser
from urllib.parse import urlencode
from tkinter import *
from tkinter import filedialog, messagebox
import concurrent.futures
import csv

# --- Настройки ---
AMO_DOMAIN = os.getenv("AMO_DOMAIN")
CLIENT_ID = os.getenv("AMO_CLIENT_ID")
CLIENT_SECRET = os.getenv("AMO_CLIENT_SECRET")
REDIRECT_URI = os.getenv("AMO_REDIRECT_URI")
TOKENS_FILE = pathlib.Path("token.json")
CSV_LOG = pathlib.Path("call_records_log.csv")
BASE_URL = f"https://{AMO_DOMAIN}.amocrm.ru"

# --- Логирование ---
logging.basicConfig(
    filename="amocrm_export.log",
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s"
)

# --- OAuth авторизация ---
def get_oauth_url():
    params = {
        "client_id": CLIENT_ID,
        "redirect_uri": REDIRECT_URI,
        "response_type": "code",
        "state": "amocrm_export"
    }
    return f"{BASE_URL}/oauth2/authorize?{urlencode(params)}"

def get_access_token(code):
    r = requests.post(f"{BASE_URL}/oauth2/access_token", json={
        "grant_type": "authorization_code",
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET,
        "code": code,
        "redirect_uri": REDIRECT_URI
    }, timeout=30)
    r.raise_for_status()
    tokens = r.json()
    tokens["expires_at"] = time.time() + tokens["expires_in"]
    TOKENS_FILE.write_text(json.dumps(tokens, indent=2, ensure_ascii=False))
    logging.info("✅ Токен сохранён")
    print("✅ Токен сохранён")
    return tokens

def load_tokens():
    if TOKENS_FILE.exists():
        tokens = json.loads(TOKENS_FILE.read_text())
        if tokens["expires_at"] > time.time() + 60:
            return tokens
        r = requests.post(f"{BASE_URL}/oauth2/access_token", json={
            "grant_type": "refresh_token",
            "client_id": CLIENT_ID,
            "client_secret": CLIENT_SECRET,
            "refresh_token": tokens["refresh_token"],
            "redirect_uri": REDIRECT_URI
        }, timeout=30)
        r.raise_for_status()
        new_tokens = r.json()
        new_tokens["expires_at"] = time.time() + new_tokens["expires_in"]
        TOKENS_FILE.write_text(json.dumps(new_tokens, indent=2, ensure_ascii=False))
        logging.info("🔄 Токен обновлён")
        return new_tokens
    raise SystemExit("❌ Токен не найден. Сначала выполните авторизацию.")

# --- API ---
def api_get(path, params=None):
    tokens = load_tokens()
    headers = {"Authorization": f"Bearer {tokens['access_token']}"}
    r = requests.get(f"{BASE_URL}{path}", headers=headers, params=params, timeout=30)
    r.raise_for_status()
    return r.json()

# --- Сбор ID сделок ---
def collect_lead_ids(args):
    if args.lead_ids:
        return [int(x) for x in args.lead_ids]
    q = {"filter[pipeline_id]": args.pipeline, "filter[statuses][0]": args.stage, "limit": 250}
    out, page = [], 1
    while True:
        q["page"] = page
        data = api_get("/api/v4/leads", q)
        chunk = data["_embedded"]["leads"]
        if not chunk:
            break
        out.extend([lead["id"] for lead in chunk])
        page += 1
    return out

# --- Получение ссылок на записи ---
def fetch_call_links(lead_id):
    q = {"filter[note_type]": "call_in,call_out", "limit": 250}
    page, links = 1, []
    while True:
        q["page"] = page
        data = api_get(f"/api/v4/leads/{lead_id}/notes", q)
        chunk = data["_embedded"]["notes"]
        if not chunk:
            break
        links += [
            note["params"]["link"] for note in chunk if "link" in note["params"]
        ]
        page += 1
    return [(lead_id, link) for link in links]

# --- Скачивание записей ---
def download_record(item, target_dir):
    lead_id, url = item
    fname = url.split("/")[-1].split("?")[0] or f"{int(time.time())}.mp3"
    path = target_dir / fname
    if path.exists():
        logging.warning(f"⚠️ Пропущено (уже есть): {fname}")
        return lead_id, fname, "уже есть"
    try:
        with requests.get(url, stream=True, timeout=60) as r:
            r.raise_for_status()
            with open(path, "wb") as f:
                for chunk in r.iter_content(8192):
                    f.write(chunk)
        logging.info(f"✔️ Скачано: {fname}")
        return lead_id, fname, "успешно"
    except Exception as e:
        logging.error(f"❌ Ошибка скачивания: {e}")
        return lead_id, fname, f"ошибка: {e}"

# --- Сохранение CSV-отчёта ---
def save_csv_log(records):
    with open(CSV_LOG, "w", newline="", encoding="utf-8") as f:
        writer = csv.writer(f)
        writer.writerow(["ID сделки", "Файл", "Статус"])
        for lead_id, fname, status in records:
            writer.writerow([lead_id, fname, status])

# --- CLI-режим ---
def cli_mode(args):
    target_dir = pathlib.Path(args.out)
    target_dir.mkdir(parents=True, exist_ok=True)

    lead_ids = collect_lead_ids(args)
    print(f"⚙️ Найдено сделок: {len(lead_ids)}")
    logging.info(f"⚙️ Найдено сделок: {len(lead_ids)}")

    all_links = []
    for lid in lead_ids:
        all_links.extend(fetch_call_links(lid))

    print(f"📞 Найдено записей: {len(all_links)}")
    logging.info(f"📞 Найдено записей: {len(all_links)}")

    results = []
    with concurrent.futures.ThreadPoolExecutor() as executor:
        futures = [executor.submit(download_record, item, target_dir) for item in all_links]
        for future in concurrent.futures.as_completed(futures):
            results.append(future.result())

    save_csv_log(results)
    print(f"✅ Загружено {len(results)} записей")
    logging.info(f"✅ Загружено {len(results)} записей")

# --- GUI-приложение ---
class AmoCRMGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("📞 amoCRM Загрузчик записей звонков")

        tk.Label(root, text="amoCRM — загрузка записей звонков", font=("Arial", 14)).pack(pady=10)

        self.mode = tk.StringVar(value="leads")

        tk.Radiobutton(root, text="ID сделок", variable=self.mode, value="leads").pack()
        self.leads_input = tk.Entry(root, width=50)
        self.leads_input.pack(pady=5)

        tk.Radiobutton(root, text="По воронке и этапу", variable=self.mode, value="pipeline").pack()
        self.pipeline_input = tk.Entry(root, width=50)
        self.pipeline_input.pack(pady=5)
        self.stage_input = tk.Entry(root, width=50)
        self.stage_input.pack(pady=5)
        tk.Label(root, text="ID воронки").pack()
        tk.Label(root, text="ID этапа").pack()

        tk.Label(root, text="Папка для сохранения:").pack(pady=5)
        self.folder_input = tk.Entry(root, width=50)
        self.folder_input.pack(pady=5)
        tk.Button(root, text="📁 Выбрать папку", command=self.select_folder).pack(pady=5)

        tk.Button(root, text="🔑 Авторизация через браузер", command=self.oauth_browser).pack(pady=5)
        tk.Button(root, text="🚀 Начать загрузку", command=self.start_download).pack(pady=10)

    def select_folder(self):
        folder = filedialog.askdirectory()
        if folder:
            self.folder_input.delete(0, tk.END)
            self.folder_input.insert(0, folder)

    def oauth_browser(self):
        url = get_oauth_url()
        print("🌐 Открываю браузер для авторизации...")
        webbrowser.open(url)
        messagebox.showinfo("Инструкция", "1. Авторизуйтесь в amoCRM\n2. Скопируйте code из URL\n3. Вставьте в терминал:\npython amocrm_export_calls.py --code <ваш_code>")

    def start_download(self):
        target_dir = self.folder_input.get()
        if not target_dir:
            messagebox.showerror("Ошибка", "❌ Укажите папку для загрузки")
            return

        if self.mode.get() == "leads":
            args = argparse.Namespace(
                lead_ids=self.leads_input.get().split(),
                pipeline=None,
                stage=None,
                out=target_dir
            )
        else:
            args = argparse.Namespace(
                lead_ids=None,
                pipeline=int(self.pipeline_input.get()),
                stage=int(self.stage_input.get()),
                out=target_dir
            )

        try:
            cli_mode(args)
            messagebox.showinfo("Готово", f"✅ Записи загружены в {target_dir}")
        except Exception as e:
            messagebox.showerror("Ошибка", str(e))

# --- Запуск CLI ---
def cli_main():
    parser = argparse.ArgumentParser(description="Выгрузка записей звонков из amoCRM")
    parser.add_argument("--code", help="Получить токен по code")
    parser.add_argument("--lead-ids", nargs="+", help="ID сделок")
    parser.add_argument("--pipeline", type=int, help="ID воронки")
    parser.add_argument("--stage", type=int, help="ID этапа")
    parser.add_argument("--out", required=True, help="Папка для сохранения")
    args = parser.parse_args()

    if args.code:
        print("🔄 Получаю токен...")
        get_access_token(args.code)
        return

    cli_mode(args)

# --- Запуск GUI ---
def gui_main():
    root = Tk()
    app = AmoCRMGUI(root)
    root.mainloop()

# --- Точка входа ---
if __name__ == "__main__":
    if "--gui" in sys.argv:
        gui_main()
    else:
        cli_main() 
C:\Users\Evgen\python_scripts>py amo.py
Traceback (most recent call last):
  File "C:\Users\Evgen\python_scripts\amo.py", line 265, in <module>
    if "--gui" in sys.argv:
                  ^^^
NameError: name 'sys' is not defined. Did you forget to import 'sys'?