#!/usr/bin/env python3
"""HTTP server for public sites + calendar .ics + lead intake endpoint.

Сайты открыты публично (для трафика с Авито/соцсетей).
POST /api/lead принимает заявки с форм сайтов, уведомляет владельца через
Telegram-бота Виктория и пишет лид в единую CRM (SQLite, crm/crm.db) —
ту же базу, которую использует crm_server.py и утренний дайджест.
"""
import http.server
import socketserver
import json
import os
import secrets
import sqlite3
import time
import urllib.request
import urllib.error
from datetime import datetime, timezone

PORT = 8765
DIRECTORY = "/home/agent/public"
AGENT_HOME = "/home/agent"
ENV_FILE = os.path.join(AGENT_HOME, ".agent", ".env")
OWNER_FILE = os.path.join(AGENT_HOME, ".agent", "owner.json")
REFS_FILE = os.path.join(AGENT_HOME, "workspace", "clients", "referrals.json")
CRM_DB_PATH = os.path.join(AGENT_HOME, "projects", "crm", "crm.db")
CRM_SCHEMA_PATH = os.path.join(AGENT_HOME, "projects", "crm", "schema.sql")
CRM_CONFIG_PATH = os.path.join(AGENT_HOME, "projects", "crm", "config.json")
STATS_FILE = os.path.join(AGENT_HOME, "workspace", "data", "podpischiki.json")
STATS_CONFIG_PATH = os.path.join(AGENT_HOME, "workspace", "data", "stats_config.json")

crm_sessions = set()  # session tokens (in-memory, cleared on restart)

SITE_TO_PROJECT = {
    "angelina-bask": "angelina-bask",
    "invest-club": "invest-club",
    "prodazha-saytov": "prodazha-saytov",
    "kladovki": "kladovki",
    "novostroyki": "novostroyki",
}

TRANSLIT = {
    'А':'A','Б':'B','В':'V','Г':'G','Д':'D','Е':'E','Ё':'E','Ж':'Zh','З':'Z',
    'И':'I','Й':'J','К':'K','Л':'L','М':'M','Н':'N','О':'O','П':'P','Р':'R',
    'С':'S','Т':'T','У':'U','Ф':'F','Х':'Kh','Ц':'Ts','Ч':'Ch','Ш':'Sh','Щ':'Sch',
    'Ъ':'','Ы':'Y','Ь':'','Э':'E','Ю':'Yu','Я':'Ya',
}

def translit(s):
    out = []
    for ch in s.upper():
        out.append(TRANSLIT.get(ch, ch if ch.isalpha() else ''))
    return ''.join(out)

def make_ref_code(last_name, first_name):
    base = last_name.strip() or first_name.strip()
    prefix = ''.join(c for c in translit(base) if c.isalpha())[:3].upper()
    prefix = (prefix + 'REF')[:3]
    suffix = str(int(time.time()))[-4:]
    return f"{prefix}{suffix}"

def load_refs():
    try:
        with open(REFS_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return []

def save_ref(entry):
    refs = load_refs()
    refs.append(entry)
    os.makedirs(os.path.dirname(REFS_FILE), exist_ok=True)
    with open(REFS_FILE, "w", encoding="utf-8") as f:
        json.dump(refs, f, ensure_ascii=False, indent=2)


def load_crm_config():
    try:
        with open(CRM_CONFIG_PATH, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return {}

def load_stats():
    try:
        with open(STATS_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return {}

def load_stats_config():
    try:
        with open(STATS_CONFIG_PATH, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return {}

def save_stats(data):
    os.makedirs(os.path.dirname(STATS_FILE), exist_ok=True)
    with open(STATS_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)


def load_env(path):
    env = {}
    try:
        with open(path, "r", encoding="utf-8") as f:
            for line in f:
                line = line.strip()
                if not line or line.startswith("#") or "=" not in line:
                    continue
                k, v = line.split("=", 1)
                env[k.strip()] = v.strip().strip('"').strip("'")
    except FileNotFoundError:
        pass
    return env


def load_owner_id():
    try:
        with open(OWNER_FILE, "r", encoding="utf-8") as f:
            return str(json.load(f).get("id"))
    except Exception:
        return None


def notify_owner_telegram(token, owner_id, text):
    if not token or not owner_id:
        return False
    url = f"https://api.telegram.org/bot{token}/sendMessage"
    data = json.dumps({
        "chat_id": owner_id,
        "text": text,
        "parse_mode": "HTML",
    }).encode("utf-8")
    req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"})
    try:
        with urllib.request.urlopen(req, timeout=10) as resp:
            return resp.status == 200
    except urllib.error.URLError:
        return False


def crm_db():
    conn = sqlite3.connect(CRM_DB_PATH)
    with open(CRM_SCHEMA_PATH, "r", encoding="utf-8") as f:
        conn.executescript(f.read())
    return conn


def save_lead(site, payload):
    project = SITE_TO_PROJECT.get(site, "other")
    name = payload.get("name", "")
    contact = payload.get("contact", "")
    message = payload.get("message", "")
    topic = payload.get("type", "") or message[:500]
    unit = payload.get("unit", "")
    ref_code = str(payload.get("ref_code", "")).strip()[:50] or None
    consent_pd = 1  # форма на сайте не даёт отправить без обязательной галочки согласия
    consent_rassylka = 1 if str(payload.get("newsletter", "")).lower() in ("true", "1", "on", "yes") else 0
    ts = datetime.now(timezone.utc).isoformat(timespec="seconds")
    source = f"referral:{ref_code}" if ref_code else f"site:{site}"

    conn = crm_db()
    cur = conn.execute(
        """INSERT INTO leads
           (project, name, contact, message, unit, source, topic, consent_pd, consent_rassylka,
            ref_code, status, created_at, updated_at)
           VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'new', ?, ?)""",
        (project, name, contact, message, unit, source, topic, consent_pd, consent_rassylka,
         ref_code, ts, ts)
    )
    conn.commit()
    lead_id = cur.lastrowid
    conn.close()
    return lead_id


SITE_LABELS = {
    "angelina-bask": "Анжелина Баск",
    "invest-club": "Инвестиции со смыслом",
    "prodazha-saytov": "Продажа сайтов/нейроагентов",
    "kladovki": "Кладовки (лендинг)",
    "novostroyki": "Новостройки и инвестиции",
}


class Handler(http.server.SimpleHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, directory=DIRECTORY, **kwargs)

    def log_message(self, format, *args):
        pass  # quiet mode

    def _send_json(self, status, body):
        data = json.dumps(body, ensure_ascii=False).encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Content-Length", str(len(data)))
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Methods", "POST, OPTIONS")
        self.send_header("Access-Control-Allow-Headers", "Content-Type")
        self.end_headers()
        self.wfile.write(data)

    def do_OPTIONS(self):
        self.send_response(200)
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Methods", "POST, OPTIONS")
        self.send_header("Access-Control-Allow-Headers", "Content-Type")
        self.send_header("Content-Length", "0")
        self.end_headers()

    def do_POST(self):
        if self.path == "/crm/login":
            return self._handle_crm_login()
        if self.path == "/api/ref":
            return self._handle_ref()
        if self.path == "/api/stats":
            return self._handle_stats_post()
        if self.path != "/api/lead":
            return self._send_json(404, {"ok": False, "error": "not found"})

        length = int(self.headers.get("Content-Length", 0))
        if length <= 0 or length > 200_000:
            return self._send_json(400, {"ok": False, "error": "bad request"})
        raw = self.rfile.read(length)
        try:
            payload = json.loads(raw.decode("utf-8"))
        except Exception:
            return self._send_json(400, {"ok": False, "error": "invalid json"})

        site = str(payload.get("site", "unknown"))[:50]
        name = str(payload.get("name", "")).strip()[:200]
        contact = str(payload.get("contact", "")).strip()[:200]
        if not name or not contact:
            return self._send_json(400, {"ok": False, "error": "name and contact required"})

        clean = {k: (str(v)[:1000] if v is not None else "") for k, v in payload.items()}
        lead_id = save_lead(site, clean)

        env = load_env(ENV_FILE)
        token = env.get("VICTORIA_BOT_TOKEN")
        owner_id = load_owner_id()
        label = SITE_LABELS.get(site, site)
        extra_lines = []
        for key in ("email", "type", "message", "budget"):
            if clean.get(key):
                extra_lines.append(f"{key}: {clean[key]}")
        ref_code = str(payload.get("ref_code", "")).strip()[:50]
        if ref_code:
            extra_lines.append(f"🤝 Реферал: <b>{ref_code}</b>")
        extra = ("\n" + "\n".join(extra_lines)) if extra_lines else ""
        text = (
            f"🌐 <b>Новая заявка с сайта «{label}»</b>\n"
            f"Имя: {name}\n"
            f"Контакт: {contact}{extra}\n"
            f"ID лида: {lead_id}"
        )
        notify_owner_telegram(token, owner_id, text)

        if site == "kladovki":
            partner_cfg = load_crm_config().get("projects", {}).get("kladovki", {})
            partner_token = partner_cfg.get("bot_token")
            partner_chat = partner_cfg.get("chat_id")
            if partner_token and partner_chat and "ТОКЕН" not in partner_token:
                notify_owner_telegram(partner_token, partner_chat, text)

        return self._send_json(200, {"ok": True, "lead_id": lead_id})

    def _handle_ref(self):
        length = int(self.headers.get("Content-Length", 0))
        if length <= 0 or length > 10_000:
            return self._send_json(400, {"ok": False, "error": "bad request"})
        try:
            payload = json.loads(self.rfile.read(length).decode("utf-8"))
        except Exception:
            return self._send_json(400, {"ok": False, "error": "invalid json"})

        first_name = str(payload.get("first_name", "")).strip()[:100]
        last_name  = str(payload.get("last_name",  "")).strip()[:100]
        phone      = str(payload.get("phone",      "")).strip()[:60]

        if not first_name or not phone:
            return self._send_json(400, {"ok": False, "error": "first_name and phone required"})

        code = make_ref_code(last_name, first_name)
        link = f"http://degtiareva-ai.ru/?ref={code}"

        save_ref({
            "code":          code,
            "first_name":    first_name,
            "last_name":     last_name,
            "phone":         phone,
            "registered_at": time.strftime("%Y-%m-%dT%H:%M:%S"),
            "link":          link,
        })

        env      = load_env(ENV_FILE)
        token    = env.get("VICTORIA_BOT_TOKEN")
        owner_id = load_owner_id()
        full_name = f"{first_name} {last_name}".strip()
        notify_owner_telegram(token, owner_id,
            f"🤝 <b>Новый партнёр (реф-программа)</b>\n"
            f"Имя: {full_name}\n"
            f"Телефон: {phone}\n"
            f"Код: <b>{code}</b>\n"
            f"Ссылка: {link}"
        )

        return self._send_json(200, {"ok": True, "code": code, "link": link})

    def _handle_stats_post(self):
        length = int(self.headers.get("Content-Length", 0))
        if length <= 0 or length > 10_000:
            return self._send_json(400, {"ok": False, "error": "bad request"})
        try:
            payload = json.loads(self.rfile.read(length).decode("utf-8"))
        except Exception:
            return self._send_json(400, {"ok": False, "error": "invalid json"})

        data = load_stats()
        pwd = str(payload.get("password", ""))
        if pwd != load_stats_config().get("admin_password"):
            return self._send_json(403, {"ok": False, "error": "wrong password"})

        today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
        snap = {
            "date": today,
            "instagram": int(payload.get("instagram", 0)),
            "vk": int(payload.get("vk", 0)),
            "telegram": int(payload.get("telegram", 0)),
            "max": int(payload.get("max", 0)),
        }
        note = str(payload.get("note", "")).strip()[:200]
        if note:
            snap["note"] = note

        snaps = data.get("snapshots", [])
        if snaps and snaps[-1]["date"] == today:
            snaps[-1] = snap
        else:
            snaps.append(snap)
        data["snapshots"] = snaps
        save_stats(data)
        return self._send_json(200, {"ok": True, "date": today, "total": sum(snap[k] for k in ("instagram","vk","telegram","max"))})

    def _serve_crm_html(self):
        html_path = os.path.join(DIRECTORY, "crm-dashboard", "index.html")
        try:
            with open(html_path, "rb") as f:
                data = f.read()
            self.send_response(200)
            self.send_header("Content-Type", "text/html; charset=utf-8")
            self.send_header("Content-Length", str(len(data)))
            self.end_headers()
            self.wfile.write(data)
        except FileNotFoundError:
            self._send_json(404, {"error": "CRM dashboard not found"})

    def _handle_crm_login(self):
        length = int(self.headers.get("Content-Length", 0))
        if length <= 0 or length > 1_000:
            return self._send_json(400, {"ok": False, "error": "bad request"})
        try:
            payload = json.loads(self.rfile.read(length).decode("utf-8"))
        except Exception:
            return self._send_json(400, {"ok": False, "error": "invalid json"})
        cfg = load_crm_config()
        pwd = cfg.get("dashboard_password")
        if str(payload.get("password", "")) != pwd:
            return self._send_json(403, {"ok": False, "error": "Неверный пароль"})
        token = secrets.token_hex(32)
        crm_sessions.add(token)
        return self._send_json(200, {"ok": True, "token": token})

    def _handle_crm_leads(self):
        auth = self.headers.get("Authorization", "")
        token = auth.replace("Bearer ", "").strip()
        if token not in crm_sessions:
            return self._send_json(401, {"ok": False, "error": "unauthorized"})
        cfg = load_crm_config()
        crm_token = cfg.get("auth_token", "")
        qs = self.path.split("?", 1)[1] if "?" in self.path else ""
        url = f"http://localhost:8770/leads?{qs}" if not qs else f"http://localhost:8770/leads?{qs}"
        req = urllib.request.Request(url, headers={"Authorization": f"Bearer {crm_token}"})
        try:
            with urllib.request.urlopen(req, timeout=5) as resp:
                data = resp.read()
            self.send_response(200)
            self.send_header("Content-Type", "application/json; charset=utf-8")
            self.send_header("Content-Length", str(len(data)))
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(data)
        except Exception as e:
            self._send_json(500, {"ok": False, "error": str(e)})

    REDIRECTS = {}

    def do_GET(self):
        path_only = self.path.split("?", 1)[0].rstrip("/")
        if path_only in ("/crm", "/crm/"):
            return self._serve_crm_html()
        if path_only == "/crm/api/leads":
            return self._handle_crm_leads()
        if path_only == "/api/stats":
            data = load_stats()
            self._send_json(200, data)
            return
        for old, new in self.REDIRECTS.items():
            if path_only == old:
                rest = self.path[len(old):]
                location = new + rest
                self.send_response(301)
                self.send_header("Location", location)
                self.send_header("Content-Length", "0")
                self.end_headers()
                return
        return super().do_GET()


socketserver.TCPServer.allow_reuse_address = True
with socketserver.TCPServer(("", PORT), Handler) as httpd:
    httpd.serve_forever()
