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

from . import utils as ut

APP_NAME = 'axi-control'
APP_AUTHOR = 'AxiControl'
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 = ut.select_file_root(artwork_root)
    app = Flask(__name__)
    ad = axc.AxiController()

    logging.getLogger('werkzeug').setLevel(logging.WARNING)
    ACCESS_LOGGER = logging.getLogger('axi_access')

    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):
    _ = frame  # unused
    logging.info('Cleaning up, signal=%s', signum)
    PID_FILE.unlink(missing_ok=True)
    sys.exit(0)


def main(artwork_root, config_dir, host: str = '0.0.0.0', port: int = 5050, dev: bool = False) -> None:
    config_dir = ut.resolve_dir(
        cli_value=config_dir,
        env_var=os.environ.get('AXICTRL_CONFIG_DIR'),
        fallback=Path(platformdirs.user_config_dir(APP_NAME, APP_AUTHOR)),
    )

    config_dir.mkdir(parents=True, exist_ok=True)

    srv_opts_path = config_dir / 'srv_options.json'
    ut.ensure_srv_options(srv_opts_path)

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

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

    log_format = '%(asctime)s %(levelname)s: %(message)s'
    logging.basicConfig(
        level=logging.INFO,
        format=log_format,
        handlers=[
            logging.FileHandler(log_filename, mode='a', encoding='utf-8'),
            logging.StreamHandler(sys.stderr),
        ],
    )

    artwork_root_value = str(artwork_root) if artwork_root else None
    app, ctx = create_app(artwork_root_value, srv_opts_path, log_filename, plot_status_log_filename)

    # if not 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)

    logging.info('Artwork root set to %s', ctx.file_root)
    logging.info('Config directory set to %s', config_dir)
    logging.info('Using srv_options at %s', srv_opts_path)

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