#!/usr/bin/env python3
import html
import json
import os
import subprocess
import time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import parse_qs, urlparse

try:
    import pymysql
except Exception:
    pymysql = None


def load_env(path="/etc/conf.d/crm"):
    env = {}
    try:
        with open(path, "r", encoding="utf-8") as fh:
            for line in fh:
                line = line.strip()
                if not line or line.startswith("#") or "=" not in line:
                    continue
                key, value = line.split("=", 1)
                env[key] = value.strip().strip('"').strip("'")
    except FileNotFoundError:
        pass
    return env


CFG = load_env()
SCHEMA_READY = False


def cfg(name, default=""):
    return os.environ.get(name) or CFG.get(name) or default


def db():
    if pymysql is None:
        raise RuntimeError("py3-pymysql is not installed")
    return pymysql.connect(
        host=cfg("MYSQL_HOST"),
        port=int(cfg("MYSQL_PORT", "3306")),
        user=cfg("MYSQL_USER"),
        password=cfg("MYSQL_PASSWORD"),
        database=cfg("MYSQL_DATABASE"),
        charset="utf8mb4",
        autocommit=True,
        cursorclass=pymysql.cursors.DictCursor,
        connect_timeout=5,
    )


SCHEMA = [
    """
    create table if not exists crm_contacts (
        id bigint unsigned not null auto_increment primary key,
        name varchar(255) not null default '',
        phone varchar(64) not null default '',
        company varchar(255) not null default '',
        status varchar(64) not null default 'new',
        notes text not null,
        assigned_user_id bigint unsigned null,
        updated_at timestamp not null default current_timestamp on update current_timestamp,
        created_at timestamp not null default current_timestamp,
        key phone_idx (phone),
        key assigned_user_idx (assigned_user_id)
    ) charset=utf8mb4
    """,
    """
    create table if not exists crm_calls (
        id bigint unsigned not null auto_increment primary key,
        contact_id bigint unsigned null,
        user_id bigint unsigned null,
        phone varchar(64) not null default '',
        direction varchar(16) not null default 'out',
        status varchar(64) not null default 'planned',
        note text not null,
        created_at timestamp not null default current_timestamp,
        key contact_idx (contact_id),
        key user_idx (user_id),
        key phone_idx (phone)
    ) charset=utf8mb4
    """,
]


def init_db():
    with db() as conn:
        with conn.cursor() as cur:
            for stmt in SCHEMA:
                cur.execute(stmt)
            ensure_column(cur, "crm_contacts", "assigned_user_id", "assigned_user_id bigint unsigned null")
            ensure_column(cur, "crm_calls", "user_id", "user_id bigint unsigned null")


def ensure_column(cur, table, column, ddl):
    cur.execute(f"show columns from {table} like %s", (column,))
    if not cur.fetchone():
        cur.execute(f"alter table {table} add column {ddl}")


def ensure_schema():
    global SCHEMA_READY
    if SCHEMA_READY:
        return
    init_db()
    SCHEMA_READY = True


def query(sql, args=None):
    ensure_schema()
    with db() as conn:
        with conn.cursor() as cur:
            cur.execute(sql, args or ())
            return cur.fetchall()


def execute(sql, args=None):
    ensure_schema()
    with db() as conn:
        with conn.cursor() as cur:
            cur.execute(sql, args or ())
            return cur.lastrowid


HTML = """<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Office-OS</title>
<style>
:root{font-family:Arial,sans-serif;color:#1c1f24;background:#eef1f4}
body{margin:0}
header{height:52px;background:#111827;color:#fff;display:flex;align-items:center;padding:0 16px;gap:16px}
header b{font-size:18px}
main{display:grid;grid-template-columns:320px 1fr;gap:12px;padding:12px}
section{background:#fff;border:1px solid #d8dde5;border-radius:6px;min-height:160px}
.pad{padding:12px}
input,textarea,select,button{font:inherit;box-sizing:border-box}
input,textarea,select{width:100%;border:1px solid #cbd2dc;border-radius:4px;padding:8px;background:#fff}
textarea{min-height:120px;resize:vertical}
button{border:1px solid #1f2937;background:#1f2937;color:#fff;border-radius:4px;padding:8px 10px;cursor:pointer}
button.secondary{background:#fff;color:#1f2937}
.row{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:8px}
.list{max-height:calc(100vh - 92px);overflow:auto}
.contact{padding:10px;border-bottom:1px solid #e5e7eb;cursor:pointer}
.contact:hover,.contact.active{background:#e8f0fe}
.muted{color:#667085;font-size:12px}
.calls{font-size:13px}
@media(max-width:760px){main{grid-template-columns:1fr}.list{max-height:260px}}
</style>
</head>
<body>
<header><b>Office-OS</b><span>By: Margo&GsmEsimDev</span><span id="state" class="muted"></span></header>
<main>
<section><div class="pad"><input id="search" placeholder="Поиск: имя, телефон, заметка"></div><div id="contacts" class="list"></div></section>
<section class="pad">
<div class="row"><input id="name" placeholder="Имя"><input id="phone" placeholder="Телефон или SIP URI"></div>
<div class="row"><input id="company" placeholder="Компания"><select id="status"><option>new</option><option>call</option><option>no_answer</option><option>deal</option><option>bad</option></select></div>
<textarea id="notes" placeholder="Заметки"></textarea>
<p>
<button onclick="save()">Сохранить</button>
<button onclick="dial()">Позвонить</button>
<button class="secondary" onclick="newContact()">Новый</button>
</p>
<h3>История</h3>
<div id="calls" class="calls"></div>
</section>
</main>
<script>
let current=null;
async function api(path, data){
  const opt=data?{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)}:{};
  const r=await fetch(path,opt); const j=await r.json(); if(!r.ok) throw new Error(j.error||r.statusText); return j;
}
function esc(s){return String(s||'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));}
async function load(){
  try{
    const q=document.getElementById('search').value;
    const data=await api('/api/contacts?q='+encodeURIComponent(q));
    document.getElementById('state').textContent='MySQL OK';
    document.getElementById('contacts').innerHTML=data.contacts.map(c=>`<div class="contact ${current&&current.id==c.id?'active':''}" onclick='pick(${JSON.stringify(c)})'><b>${esc(c.name||c.phone)}</b><div>${esc(c.phone)}</div><div class="muted">${esc(c.status)} ${esc(c.updated_at)}</div></div>`).join('');
  }catch(e){document.getElementById('state').textContent='ERR '+e.message;}
}
async function pick(c){current=c; for(const k of ['name','phone','company','status','notes']) document.getElementById(k).value=c[k]||''; await loadCalls(); await load();}
function form(){return {id:current&&current.id,name:name.value,phone:phone.value,company:company.value,status:status.value,notes:notes.value};}
async function save(){const r=await api('/api/contact',form()); current=r.contact; await load(); await loadCalls();}
async function dial(){const r=await api('/api/dial',{contact_id:current&&current.id,phone:phone.value}); await loadCalls(); alert(r.message);}
async function loadCalls(){if(!current){calls.innerHTML='';return} const r=await api('/api/calls?contact_id='+current.id); calls.innerHTML=r.calls.map(c=>`<p><b>${esc(c.status)}</b> ${esc(c.created_at)}<br>${esc(c.note)}</p>`).join('');}
function newContact(){current=null; for(const k of ['name','phone','company','notes']) document.getElementById(k).value=''; status.value='new'; calls.innerHTML=''; load();}
search.oninput=()=>load();
load();
</script>
</body></html>"""


class Handler(BaseHTTPRequestHandler):
    def send_json(self, obj, code=200):
        data = json.dumps(obj, ensure_ascii=False, default=str).encode()
        self.send_response(code)
        self.send_header("content-type", "application/json; charset=utf-8")
        self.send_header("content-length", str(len(data)))
        self.end_headers()
        self.wfile.write(data)

    def read_json(self):
        length = int(self.headers.get("content-length", "0"))
        return json.loads(self.rfile.read(length) or b"{}")

    def do_GET(self):
        parsed = urlparse(self.path)
        try:
            if parsed.path == "/":
                data = HTML.encode()
                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)
            elif parsed.path == "/api/contacts":
                q = parse_qs(parsed.query).get("q", [""])[0]
                like = f"%{q}%"
                rows = query(
                    "select id,name,phone,company,status,notes,updated_at from crm_contacts "
                    "where %s='' or name like %s or phone like %s or company like %s or notes like %s "
                    "order by updated_at desc limit 200",
                    (q, like, like, like, like),
                )
                self.send_json({"contacts": rows})
            elif parsed.path == "/api/calls":
                contact_id = parse_qs(parsed.query).get("contact_id", ["0"])[0]
                rows = query(
                    "select id,contact_id,phone,direction,status,note,created_at from crm_calls "
                    "where contact_id=%s order by id desc limit 100",
                    (contact_id,),
                )
                self.send_json({"calls": rows})
            else:
                self.send_error(404)
        except Exception as exc:
            self.send_json({"error": str(exc)}, 500)

    def do_POST(self):
        try:
            if self.path == "/api/contact":
                data = self.read_json()
                if data.get("id"):
                    execute(
                        "update crm_contacts set name=%s,phone=%s,company=%s,status=%s,notes=%s where id=%s",
                        (data.get("name",""), data.get("phone",""), data.get("company",""), data.get("status","new"), data.get("notes",""), data["id"]),
                    )
                    cid = data["id"]
                else:
                    cid = execute(
                        "insert into crm_contacts(name,phone,company,status,notes) values(%s,%s,%s,%s,%s)",
                        (data.get("name",""), data.get("phone",""), data.get("company",""), data.get("status","new"), data.get("notes","")),
                    )
                row = query("select id,name,phone,company,status,notes,updated_at from crm_contacts where id=%s", (cid,))[0]
                self.send_json({"contact": row})
            elif self.path == "/api/dial":
                data = self.read_json()
                phone = data.get("phone", "")
                cid = data.get("contact_id")
                execute(
                    "insert into crm_calls(contact_id,phone,direction,status,note) values(%s,%s,'out','dial','dial requested')",
                    (cid, phone),
                )
                try:
                    subprocess.Popen(["/usr/local/bin/crm-softphone", "dial", phone])
                    msg = "dial started"
                except Exception as exc:
                    msg = f"logged, softphone not started: {exc}"
                self.send_json({"message": msg})
            else:
                self.send_error(404)
        except Exception as exc:
            self.send_json({"error": str(exc)}, 500)


def main():
    try:
        init_db()
    except Exception as exc:
        print(f"MySQL not ready: {exc}", flush=True)
    host = cfg("CRM_HOST", "0.0.0.0")
    port = int(cfg("CRM_PORT", "8080"))
    print(f"CRM listening on {host}:{port}", flush=True)
    ThreadingHTTPServer((host, port), Handler).serve_forever()


if __name__ == "__main__":
    main()
