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

import platformdirs
from flask import Flask, g, request

import axi_control as axc

APP_NAME = 'axi-control'
APP_AUTHOR = 'FloRaths'
PID_FILE = Path('/tmp/axi_server.pid')


SKIP_LOG_PATHS = {'/log', '/axidraw_status', '/motor_state'}
# ˆ these endpoints are polled frequently by the frontend, so we skip logging them to reduce noise in the access log

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


def create_app(artwork_root: str | None, srv_opts_path: Path, log_filename: Path, plot_status_log_filename: Path):
    file_root = axc.ut.select_file_root(artwork_root)
    app = Flask(__name__)
    ad = axc.AxiController()

    ctx = axc.ServerContext(
        ad=ad,
        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 HARDWARE_LOCK_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()


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.ut.ensure_srv_options(srv_opts_path)

    log_filename = log_dir / 'axiserver.log'
    plot_status_log_filename = log_dir / 'plot_status.log'
    last_port_file = log_dir / 'axiserver.port'

    if last_port_file.exists():
        last_port = last_port_file.read_text(encoding='utf-8').strip()
        if last_port == str(args.port):
            log_filename.unlink(missing_ok=True)
    last_port_file.write_text(str(args.port), encoding='utf-8')

    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()
