# -*- coding: utf-8 -*-
"""봉수 금융데이터분석전문가 과정 · 5강(2부 2-2강) — 서버와 데이터베이스
동네주문 서버: 손님 화면과 사장님 화면이 '같은 주문'을 보게 만든다.
- 설치할 것 없음: 파이썬에 들어 있는 http.server + sqlite3 만 씀
- 실행: 서버켜기.bat 더블클릭 (또는 python server.py)
- 주문은 같은 폴더의 orders.db(데이터베이스 파일)에 저장 → 서버를 껐다 켜도 남는다
저자 조봉수 · 봉수 SSGlobal(주) · AI(Claude)와 함께 제작 · 2026-10-22
"""
import json, sqlite3, socket, os, time, datetime
from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler

HERE = os.path.dirname(os.path.abspath(__file__))
DB = os.path.join(HERE, "orders.db")
PORT = 8000

# ============================================================
# [데이터] 가게·메뉴 — 이제 메뉴는 '서버'가 가지고 있다
# ============================================================
SHOP = {"name": "예시 망원 김밥", "deliveryFee": 2000}
MENU = [
    {"id": "m1", "name": "원조 김밥", "price": 4000},
    {"id": "m2", "name": "참치 김밥", "price": 5000},
    {"id": "m3", "name": "떡볶이", "price": 6500},
    {"id": "m4", "name": "라면", "price": 5000},
]
# 주문 상태가 될 수 있는 값 — [6단계] 여기에 "done"(배달 완료)을 더한다
ALLOWED = ["new", "accepted", "riding", "done"]   # 6단계 정답

# ============================================================
# [데이터베이스] 표 하나 = orders. 한 줄 = 주문 한 건
# ============================================================
def db():
    con = sqlite3.connect(DB)
    con.execute("""CREATE TABLE IF NOT EXISTS orders(
        no TEXT PRIMARY KEY,   -- 주문번호
        items TEXT,            -- 메뉴×개수
        sub INTEGER, fee INTEGER, discount INTEGER, total INTEGER,
        pickup INTEGER,        -- 포장 1, 배달 0
        status TEXT,           -- new / accepted / riding ...
        created TEXT)          -- 주문 시각
    """)
    return con

# ============================================================
# [계산] 돈 계산은 서버 한 곳에서만 — 손님 화면이 보낸 금액은 믿지 않는다
# ============================================================
def price(cart, pickup):
    sub = 0; names = []
    for m in MENU:
        q = int(cart.get(m["id"], 0) or 0)
        if q < 0 or q > 50: raise ValueError("개수가 이상합니다")
        if q: sub += m["price"] * q; names.append(f'{m["name"]}×{q}')
    fee = SHOP["deliveryFee"] if sub > 0 else 0
    discount = 0
    if pickup and sub > 0: fee = 0; discount = 1000
    return sub, fee, discount, sub + fee - discount, ", ".join(names)

# ============================================================
# [창구] 주소(URL)마다 할 일을 정한다 — 이것이 API
# ============================================================
class Handler(SimpleHTTPRequestHandler):
    def __init__(self, *a, **k): super().__init__(*a, directory=os.path.join(HERE, "web"), **k)

    def send_json(self, obj, code=200):
        b = json.dumps(obj, ensure_ascii=False).encode("utf-8")
        self.send_response(code); self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Content-Length", str(len(b))); self.end_headers(); self.wfile.write(b)

    def body(self):
        n = int(self.headers.get("Content-Length") or 0)
        return json.loads(self.rfile.read(n) or b"{}")

    def do_GET(self):
        if self.path == "/api/menu":                       # 메뉴 주세요
            return self.send_json({"shop": SHOP, "menu": MENU})
        if self.path == "/api/orders":                     # 주문 목록 주세요
            con = db(); con.row_factory = sqlite3.Row
            rows = [dict(r) for r in con.execute("SELECT * FROM orders ORDER BY created DESC")]
            con.close(); return self.send_json(rows)
        if self.path == "/": self.path = "/customer.html"
        return super().do_GET()                            # 그 밖에는 화면 파일

    def do_POST(self):
        try:
            if self.path == "/api/orders":                 # 주문 받아 주세요
                d = self.body()
                sub, fee, dc, total, items = price(d.get("cart", {}), bool(d.get("pickup")))
                if sub == 0: return self.send_json({"error": "메뉴를 먼저 담아 주세요"}, 400)
                no = str(int(time.time() * 1000))[-6:]
                now = datetime.datetime.now().isoformat(timespec="seconds")
                con = db(); con.execute("INSERT INTO orders VALUES(?,?,?,?,?,?,?,?,?)", (no, items, sub, fee, dc, total, int(bool(d.get("pickup"))), "new", now))
                con.commit(); con.close()
                print(f"[주문] #{no} {items} = {total:,}원")
                return self.send_json({"no": no, "total": total})
            if self.path.startswith("/api/orders/") and self.path.endswith("/status"):   # 상태 바꿔 주세요
                no = self.path.split("/")[3]; st = self.body().get("status")
                if st not in ALLOWED: return self.send_json({"error": f"허용되지 않은 상태: {st}"}, 400)
                con = db(); n = con.execute("UPDATE orders SET status=? WHERE no=?", (st, no)).rowcount; con.commit(); con.close()
                if not n: return self.send_json({"error": "없는 주문번호"}, 404)
                print(f"[상태] #{no} → {st}")
                return self.send_json({"no": no, "status": st})
            return self.send_json({"error": "없는 주소"}, 404)
        except (ValueError, json.JSONDecodeError) as e:
            return self.send_json({"error": str(e)}, 400)

    def log_message(self, *a): pass                       # 접속 기록은 숨김(주문·상태만 출력)

def my_ip():
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.connect(("8.8.8.8", 80)); ip = s.getsockname()[0]; s.close(); return ip
    except OSError: return "127.0.0.1"

if __name__ == "__main__":
    db().close()
    print("=" * 56)
    print(" 동네주문 서버가 켜졌습니다 (끄려면 이 창에서 Ctrl+C)")
    print(f" 이 PC  손님 화면   : http://localhost:{PORT}/customer.html")
    print(f" 이 PC  사장님 화면 : http://localhost:{PORT}/owner.html")
    print(f" 휴대폰(같은 와이파이): http://{my_ip()}:{PORT}/customer.html")
    print(" 주문 저장 파일      :", DB)
    print("=" * 56)
    ThreadingHTTPServer(("0.0.0.0", PORT), Handler).serve_forever()
