import os
import time
import json
import atexit
import signal
import socket
import threading
import tempfile
import webbrowser
import subprocess
from urllib.request import urlopen, Request
from urllib.error import URLError, HTTPError

from waitress import serve


HOST = "127.0.0.1"
PORT_START = 5000
PORT_END = 5010
APP_NAME = "SatTakip"
BROWSER_TIMEOUT_SECONDS = 8
PING_TIMEOUT_SECONDS = 10

shutdown_event = threading.Event()
server_port = None
app = None


def get_local_appdata_dir():
    local_appdata = os.environ.get("LOCALAPPDATA")
    if not local_appdata:
        local_appdata = os.path.join(os.path.expanduser("~"), "AppData", "Local")

    appdata_dir = os.path.join(local_appdata, APP_NAME)
    os.makedirs(appdata_dir, exist_ok=True)
    return appdata_dir


def configure_database_path():
    appdata_dir = get_local_appdata_dir()
    db_path = os.path.join(appdata_dir, "veri.db")
    os.environ["SATIS_TAKIP_DB_PATH"] = db_path
    return db_path


def load_app_after_env():
    global app
    from app import app as flask_app
    from core.db import init_db
    app = flask_app
    return init_db


def get_lock_file_path():
    return os.path.join(tempfile.gettempdir(), f"{APP_NAME}.lock")


def is_process_alive(pid):
    if pid <= 0:
        return False

    try:
        if os.name == "nt":
            result = subprocess.run(
                ["tasklist", "/FI", f"PID eq {pid}"],
                capture_output=True,
                text=True,
                creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0)
            )
            return str(pid) in result.stdout
        else:
            os.kill(pid, 0)
            return True
    except Exception:
        return False


def read_lock_info():
    lock_path = get_lock_file_path()
    if not os.path.exists(lock_path):
        return None

    try:
        with open(lock_path, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return None


def write_lock_info(port):
    lock_path = get_lock_file_path()
    data = {"pid": os.getpid(), "port": port}
    with open(lock_path, "w", encoding="utf-8") as f:
        json.dump(data, f)


def remove_lock_info():
    lock_path = get_lock_file_path()
    try:
        if os.path.exists(lock_path):
            os.remove(lock_path)
    except Exception:
        pass


def wait_until_server_ready(port, timeout=10):
    url = f"http://{HOST}:{port}/api/durum"
    end_time = time.time() + timeout

    while time.time() < end_time:
        try:
            req = Request(url, headers={"Cache-Control": "no-cache"})
            with urlopen(req, timeout=2) as response:
                if response.status == 200:
                    return True
        except (URLError, HTTPError, OSError):
            time.sleep(0.4)

    return False


def get_existing_instance_port():
    info = read_lock_info()
    if not info:
        return None

    pid = int(info.get("pid", 0) or 0)
    port = int(info.get("port", 0) or 0)

    if not is_process_alive(pid):
        remove_lock_info()
        return None

    if port < PORT_START or port > PORT_END:
        remove_lock_info()
        return None

    if wait_until_server_ready(port, timeout=2):
        return port

    return None


def is_port_free(port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(0.5)
    try:
        sock.bind((HOST, port))
        return True
    except OSError:
        return False
    finally:
        sock.close()


def find_available_port():
    for port in range(PORT_START, PORT_END + 1):
        if is_port_free(port):
            return port
    raise RuntimeError("5000-5010 araliginda bos port bulunamadi.")


def open_browser(url):
    try:
        opened = webbrowser.open(url)
        if opened:
            return
    except Exception:
        pass

    browser_candidates = [
        r"C:\Program Files\Google\Chrome\Application\chrome.exe",
        r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
        r"C:\Program Files\Mozilla Firefox\firefox.exe",
        r"C:\Program Files (x86)\Mozilla Firefox\firefox.exe",
        r"C:\Program Files\Microsoft\Edge\Application\msedge.exe",
        r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
    ]

    for browser_path in browser_candidates:
        if os.path.exists(browser_path):
            try:
                subprocess.Popen(
                    [browser_path, url],
                    stdout=subprocess.DEVNULL,
                    stderr=subprocess.DEVNULL
                )
                return
            except Exception:
                pass

    try:
        subprocess.Popen(["cmd", "/c", "start", "", url], shell=False)
    except Exception:
        pass


def get_browser_state(port):
    browser_url = f"http://{HOST}:{port}/api/browser-state"
    try:
        req = Request(browser_url, headers={"Cache-Control": "no-cache"})
        with urlopen(req, timeout=2) as response:
            if response.status != 200:
                return None
            return json.loads(response.read().decode("utf-8"))
    except Exception:
        return None


def browser_watch_loop(port):
    browser_seen = False
    last_ping = 0.0
    start_time = time.time()

    while not shutdown_event.is_set():
        state = get_browser_state(port)
        now = time.time()

        if state:
            browser_last_ping = float(state.get("last_ping", 0.0) or 0.0)
            if browser_last_ping > 0:
                last_ping = browser_last_ping
                browser_seen = True

        if not browser_seen:
            if now - start_time > BROWSER_TIMEOUT_SECONDS:
                shutdown_event.set()
                break
        else:
            if last_ping > 0 and (now - last_ping > PING_TIMEOUT_SECONDS):
                shutdown_event.set()
                break

        time.sleep(1.0)


def run_server(port):
    serve(app, host=HOST, port=port, threads=8)


def cleanup():
    remove_lock_info()


def exit_app():
    cleanup()
    os._exit(0)


def handle_exit(*args):
    if not shutdown_event.is_set():
        shutdown_event.set()


def main():
    global server_port

    signal.signal(signal.SIGINT, handle_exit)
    signal.signal(signal.SIGTERM, handle_exit)
    atexit.register(cleanup)

    configure_database_path()
    init_db = load_app_after_env()

    existing_port = get_existing_instance_port()
    if existing_port:
        open_browser(f"http://{HOST}:{existing_port}")
        return

    init_db()

    server_port = find_available_port()
    write_lock_info(server_port)

    server_thread = threading.Thread(target=run_server, args=(server_port,), daemon=True)
    server_thread.start()

    if not wait_until_server_ready(server_port, timeout=10):
        raise RuntimeError("Sunucu baslatilamadi.")

    app_url = f"http://{HOST}:{server_port}"
    open_browser(app_url)

    watcher_thread = threading.Thread(target=browser_watch_loop, args=(server_port,), daemon=True)
    watcher_thread.start()

    while not shutdown_event.is_set():
        time.sleep(0.5)

    exit_app()


if __name__ == "__main__":
    main()