"""«Заметки» — учебное приложение курса «Docker: контейнеры и образы».

Только стандартная библиотека Python 3.12. Настройки — переменные окружения:
  HOST           адрес, на котором слушать (по умолчанию 0.0.0.0 — все адреса контейнера)
  PORT           порт (по умолчанию 8000)
  DATA_DIR       папка с данными (по умолчанию /data)
  GREETING       заголовок главной страницы (по умолчанию «Заметки»)
  STARTUP_DELAY  сколько секунд «прогреваться» после запуска; всё это время /health отвечает 503

Адреса:
  GET  /         главная: заголовок и число заметок
  GET  /notes    все заметки, по одной в строке
  POST /notes    сохранить заметку (тело запроса — текст, до 1000 символов)
  GET  /health   200 ok — готово; 503 — ещё прогревается, нет доступа к данным или включено обслуживание
"""
import json
import os
import signal
import sys
import threading
import time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

HOST = os.environ.get("HOST", "0.0.0.0")
DATA_DIR = os.environ.get("DATA_DIR", "/data")
GREETING = os.environ.get("GREETING", "Заметки")
NOTES = os.path.join(DATA_DIR, "notes.json")
MAINTENANCE = os.path.join(DATA_DIR, "maintenance")


def log(message):
    print(f"notes: {message}", flush=True)


try:
    PORT = int(os.environ.get("PORT", "8000"))
    if not 1 <= PORT <= 65535:
        raise ValueError
except ValueError:
    log(f"неверный PORT: {os.environ.get('PORT')!r} (нужно число от 1 до 65535)")
    sys.exit(2)

try:
    DELAY = float(os.environ.get("STARTUP_DELAY", "0"))
except ValueError:
    DELAY = 0.0

try:
    os.makedirs(DATA_DIR, exist_ok=True)
    probe = os.path.join(DATA_DIR, ".write-test")
    with open(probe, "w") as f:
        f.write("ok")
    os.remove(probe)
except OSError as e:
    log(f"не могу писать в {DATA_DIR}: {e.strerror}")
    sys.exit(1)

started = time.monotonic()
lock = threading.Lock()


def load():
    try:
        with open(NOTES, encoding="utf-8") as f:
            return json.load(f)
    except FileNotFoundError:
        return []


def save(notes):
    tmp = NOTES + ".tmp"
    with open(tmp, "w", encoding="utf-8") as f:
        json.dump(notes, f, ensure_ascii=False)
    os.replace(tmp, NOTES)


def ready():
    if time.monotonic() - started < DELAY:
        return False, "ещё прогревается"
    if os.path.exists(MAINTENANCE):
        return False, "включено обслуживание"
    if not os.access(DATA_DIR, os.W_OK):
        return False, "нет доступа к данным"
    return True, "ok"


class Handler(BaseHTTPRequestHandler):
    server_version = "notes/1.0"

    def reply(self, status, text):
        body = text.encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", "text/plain; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.send_header("X-App", "rpd-notes")
        self.end_headers()
        if self.command != "HEAD":
            self.wfile.write(body)
        log(f"{self.command} {self.path} {status}")

    def do_GET(self):
        if self.path == "/":
            return self.reply(200, f"{GREETING}\nЗаметок: {len(load())}\n")
        if self.path == "/notes":
            return self.reply(200, "".join(f"{n}\n" for n in load()))
        if self.path == "/health":
            ok, why = ready()
            return self.reply(200 if ok else 503, why + "\n")
        return self.reply(404, "Не найдено\n")

    do_HEAD = do_GET

    def do_POST(self):
        if self.path != "/notes":
            return self.reply(404, "Не найдено\n")
        length = int(self.headers.get("Content-Length") or 0)
        text = self.rfile.read(min(length, 4000)).decode("utf-8", "replace").strip()
        if not text or len(text) > 1000:
            return self.reply(400, "Заметка — от 1 до 1000 символов\n")
        try:
            with lock:
                notes = load()
                notes.append(text)
                save(notes)
        except OSError as e:
            return self.reply(507, f"Не удалось сохранить: {e.strerror}\n")
        return self.reply(201, f"Сохранено. Заметок: {len(notes)}\n")

    def log_message(self, *args):
        pass


server = ThreadingHTTPServer((HOST, PORT), Handler)


def stop(signum, frame):
    log("получен сигнал остановки, завершаю работу")
    threading.Thread(target=server.shutdown).start()


signal.signal(signal.SIGTERM, stop)
log(f"слушаю {HOST}:{PORT}, данные в {DATA_DIR}")
server.serve_forever()
log("остановлено")
