import argparse
import logging
import os
import signal
import sys
from pathlib import Path

import platformdirs
from axidrawinternal.plot_utils_import import from_dependency_import
from flask import Flask, g, request

import axi_control as axc

APP_NAME = "axi-control"
APP_AUTHOR = "AxiControl"
PID_FILE = Path("/tmp/axi_server.pid")

LOCKED_ENDPOINTS = {
    "nudge_x",
    "nudge_y",
    "home",
    "move_xy",
    "move_to",
    "set_home",
    "toggle_pen",
    "cycle_pen",
    "disable_motors",
    "enable_motors",
    "prime",
    "motor_state",
}


def create_app(artwork_root: str | None, srv_opts_path: Path, log_filename: Path, plot_status_log_filename: Path):
    file_root = axc.utils.select_file_root(artwork_root)
    app = Flask(__name__)
    ad = axc.AxiController()
    ebb_serial = from_dependency_import("plotink.ebb_serial")
    ctx = axc.ServerContext(
        ad=ad,
        ebb_serial=ebb_serial,
        file_root=file_root,
        srv_opts_path=srv_opts_path,
        log_filename=log_filename,
        plot_status_log_filename=plot_status_log_filename,
    )

    @app.before_request
    def _lock_ad_access():
        if request.endpoint in LOCKED_ENDPOINTS:
            ctx.ad_lock.acquire()
            g.ad_lock_acquired = True

    @app.teardown_request
    def _unlock_ad_access(_exc):
        if getattr(g, "ad_lock_acquired", False):
            ctx.ad_lock.release()

    @app.after_request
    def _log_access(response):
        if request.path in SKIP_LOG_PATHS:
            return response
        remote_addr = request.remote_addr or "-"
        path = request.full_path.rstrip("?")
        size = response.calculate_content_length()
        size_text = size if size is not None else "-"
        ACCESS_LOGGER.info(
            "%s \"%s %s\" %s %s",
            remote_addr,
            request.method,
            path,
            response.status_code,
            size_text,
        )
        return response

    axc.control.register(app, ctx)
    axc.status.register(app, ctx)
    axc.plotting.register(app, ctx)
    return app, ctx


def cleanup(signum=None, frame=None):
    logging.info("Cleaning up, signal=%s", signum)
    PID_FILE.unlink(missing_ok=True)
    sys.exit(0)


def _resolve_dir(cli_value: str | None, env_var: str | None, fallback: Path) -> Path:
    if cli_value:
        return Path(cli_value).expanduser().resolve()
    if env_var:
        return Path(env_var).expanduser().resolve()
    return fallback


def _parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="AxiControl Flask server")
    parser.add_argument(
        "--dev",
        action="store_true",
        help="Skip hardware connection checks for local development",
    )
    parser.add_argument(
        "--artwork-root",
        default=None,
        help="Root directory for artwork SVGs (overrides ARTWORK_ROOT).",
    )
    parser.add_argument(
        "--config-dir",
        default=None,
        help="Directory to store configuration (overrides AXI_CONTROL_CONFIG_DIR).",
    )
    parser.add_argument(
        "--log-dir",
        default=None,
        help="Directory to store logs (overrides AXI_CONTROL_LOG_DIR).",
    )
    parser.add_argument(
        "--srv-options",
        default=None,
        help="Path to srv_options.json (overrides AXI_CONTROL_SRV_OPTIONS).",
    )
    parser.add_argument(
        "--host",
        default="0.0.0.0",
        help="Host to bind the server.",
    )
    parser.add_argument(
        "--port",
        default=5050,
        type=int,
        help="Port to bind the server.",
    )
    return parser.parse_args()


SKIP_LOG_PATHS = {}
logging.getLogger("werkzeug").setLevel(logging.WARNING)
ACCESS_LOGGER = logging.getLogger("axi_access")


def main() -> None:
    args = _parse_args()

    config_dir = _resolve_dir(
        args.config_dir,
        os.environ.get("AXI_CONTROL_CONFIG_DIR"),
        Path(platformdirs.user_config_dir(APP_NAME, APP_AUTHOR)),
    )
    log_dir = _resolve_dir(
        args.log_dir,
        os.environ.get("AXI_CONTROL_LOG_DIR"),
        Path(platformdirs.user_log_dir(APP_NAME, APP_AUTHOR)),
    )
    config_dir.mkdir(parents=True, exist_ok=True)
    log_dir.mkdir(parents=True, exist_ok=True)

    srv_opts_path = args.srv_options or os.environ.get("AXI_CONTROL_SRV_OPTIONS")
    if srv_opts_path:
        srv_opts_path = Path(srv_opts_path).expanduser().resolve()
    else:
        srv_opts_path = config_dir / "srv_options.json"

    axc.utils.ensure_srv_options(srv_opts_path)

    log_filename = log_dir / "axiserver.log"
    plot_status_log_filename = log_dir / "plot_status.log"

    logging.basicConfig(
        filename=log_filename,
        filemode="a",
        level=logging.INFO,
        format="%(asctime)s %(levelname)s: %(message)s",
    )

    app, ctx = create_app(args.artwork_root, srv_opts_path, log_filename, plot_status_log_filename)
    logging.info("Artwork root set to %s", ctx.file_root)
    logging.info("Using srv_options at %s", srv_opts_path)
    logging.info("Logs directory set to %s", log_dir)

    if not args.dev:
        if not ctx.ad.connect():
            logging.error("AxiController connection failed")
            raise RuntimeError("AxiController connection failed")

    PID_FILE.write_text(str(os.getpid()))

    signal.signal(signal.SIGTERM, cleanup)
    signal.signal(signal.SIGINT, cleanup)

    app.run(host=args.host, port=args.port, threaded=True)


if __name__ == "__main__":
    main()
