# -*- coding: utf-8 -*-
# dzw_phone_server.py — 휴대폰 AR 접속용 HTTPS 서버 [CEO 2026-07-06]
#   폰 브라우저는 HTTP(로컬IP)에서 카메라(getUserMedia)를 차단 → HTTPS 필수.
#   자체서명 인증서를 즉석 생성(cryptography 있으면) 후 8443 포트로 뷰어 폴더를 서빙.
#   폰에서 https://<PC-IP>:8443/DOZIKWORKS_OS_v5_locked.dc.html 접속 → "고급→계속" 후 AR 사용.
import http.server, ssl, socket, os, sys

ROOT = os.path.dirname(os.path.abspath(__file__))
PORT = 8444
CERT = os.path.join(ROOT, '_phone_cert.pem')
# [2026-07-18] Tailscale 정식 인증서 — 모든 기기가 원래 신뢰함 → 경고 없이 접속.
# 재발급: tailscale cert --cert-file _ts_cert.pem --key-file _ts_key.pem desktop-da88jsg.taile11e65.ts.net
TS_CERT = os.path.join(ROOT, '_ts_cert.pem')
TS_KEY = os.path.join(ROOT, '_ts_key.pem')
TS_NAME = 'desktop-da88jsg.taile11e65.ts.net'

def local_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try: s.connect(('8.8.8.8', 80)); ip = s.getsockname()[0]
    except Exception: ip = '127.0.0.1'
    finally: s.close()
    return ip

def make_cert():
    if os.path.exists(CERT) and os.path.getsize(CERT) > 0: return True
    try:
        from cryptography import x509
        from cryptography.x509.oid import NameOID
        from cryptography.hazmat.primitives import hashes, serialization
        from cryptography.hazmat.primitives.asymmetric import rsa
        import datetime, ipaddress
        now = datetime.datetime.now(datetime.timezone.utc)
        key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
        ip = local_ip()
        name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, ip)])
        san = x509.SubjectAlternativeName([x509.IPAddress(ipaddress.ip_address(ip)),
                                           x509.DNSName('localhost')])
        cert = (x509.CertificateBuilder().subject_name(name).issuer_name(name)
                .public_key(key.public_key()).serial_number(x509.random_serial_number())
                .not_valid_before(now - datetime.timedelta(days=1))
                .not_valid_after(now + datetime.timedelta(days=825))
                .add_extension(san, critical=False)
                .sign(key, hashes.SHA256()))
        with open(CERT, 'wb') as f:
            f.write(key.private_bytes(serialization.Encoding.PEM,
                    serialization.PrivateFormat.TraditionalOpenSSL, serialization.NoEncryption()))
            f.write(cert.public_bytes(serialization.Encoding.PEM))
        return True
    except ImportError:
        return False

class H(http.server.SimpleHTTPRequestHandler):
    def __init__(self, *a, **k): super().__init__(*a, directory=ROOT, **k)
    def end_headers(self):
        self.send_header('Cross-Origin-Opener-Policy', 'same-origin')
        # [2026-07-18] 캐시 금지 — 태블릿이 옛 버전 화면을 계속 쓰던 문제. 매번 새 파일 확인 강제.
        self.send_header('Cache-Control', 'no-cache, must-revalidate')
        super().end_headers()
    def log_message(self, *a): pass
    # [2026-07-18] 기기 화면 진단 수신 — 폰/태블릿이 자기 화면 정보를 보내면 파일로 기록
    def do_POST(self):
        if self.path == '/dbg':
            n = int(self.headers.get('Content-Length', 0))
            body = self.rfile.read(n).decode('utf-8', 'replace')
            ua = self.headers.get('User-Agent', '?')
            with open(os.path.join(ROOT, '_device_debug.log'), 'a', encoding='utf-8') as f:
                f.write('[%s] UA=%s\n  %s\n' % (self.log_date_time_string(), ua, body))
            self.send_response(200); self.end_headers(); self.wfile.write(b'ok')
        else:
            self.send_response(404); self.end_headers()

if __name__ == '__main__':
    ip = local_ip()
    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    if os.path.exists(TS_CERT) and os.path.exists(TS_KEY):
        ctx.load_cert_chain(TS_CERT, TS_KEY)
        addr = TS_NAME
        note = ' ※ 정식 인증서 — 경고 없이 바로 열림 (기기에 Tailscale 로그인만 필요)'
    else:
        if not make_cert():
            print('[폰서버] cryptography 미설치 — 설치: pip install cryptography'); sys.exit(1)
        ctx.load_cert_chain(CERT)
        addr = ip
        note = ' ※ 폰 브라우저 "주의요함 → 고급 → 계속 접속" 누르면 됨'
    # 보안: Tailscale 주소에만 바인딩 — 공인 IP로는 안 열림 (2026-07-18)
    httpd = http.server.ThreadingHTTPServer(('100.99.178.47', PORT), H)
    httpd.socket = ctx.wrap_socket(httpd.socket, server_side=True)
    print('=' * 56)
    print(' 휴대폰/태블릿 접속 주소:')
    print('   https://%s:%d/DOZIKWORKS_OS_v5_locked.dc.html' % (addr, PORT))
    print(note)
    print(' ※ AR: 우측 하단 [AR] 버튼 → 실물 8점(꾹 눌러 돋보기로 조준) → 3D 8점 같은 순서')
    print('=' * 56)
    httpd.serve_forever()
