from __future__ import annotations

import hashlib
import json
import os
import platform
import secrets
import sqlite3
from datetime import datetime, timedelta, timezone
from pathlib import Path

ROOT_DIR = Path(__file__).resolve().parent.parent
APP_CONFIG_FILE = ROOT_DIR / "app_config.json"
LICENSE_DB_FILE = ROOT_DIR / "license_server.db"
APP_VERSION = "0.1.0-dev"


def load_app_config() -> dict:
    if not APP_CONFIG_FILE.exists():
        return {}

    try:
        return json.loads(APP_CONFIG_FILE.read_text(encoding="utf-8"))
    except json.JSONDecodeError:
        return {}


def save_app_config(config: dict) -> None:
    APP_CONFIG_FILE.write_text(
        json.dumps(config, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )


def get_db_connection() -> sqlite3.Connection:
    connection = sqlite3.connect(LICENSE_DB_FILE)
    connection.row_factory = sqlite3.Row
    return connection


def init_license_db() -> None:
    with get_db_connection() as connection:
        connection.execute(
            """
            CREATE TABLE IF NOT EXISTS admin_users (
                email TEXT PRIMARY KEY,
                status TEXT NOT NULL,
                plan_name TEXT NOT NULL,
                expires_at TEXT NOT NULL,
                device_fingerprint TEXT NOT NULL DEFAULT '',
                device_bound_at TEXT NOT NULL DEFAULT '',
                last_seen_at TEXT NOT NULL DEFAULT '',
                notes TEXT NOT NULL DEFAULT '',
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL
            )
            """
        )
        connection.execute(
            """
            CREATE TABLE IF NOT EXISTS admin_messages (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                message_type TEXT NOT NULL,
                target_email TEXT NOT NULL DEFAULT '',
                title TEXT NOT NULL,
                body TEXT NOT NULL,
                is_active INTEGER NOT NULL DEFAULT 1,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                UNIQUE(message_type, target_email)
            )
            """
        )
        connection.execute(
            """
            CREATE TABLE IF NOT EXISTS notice_posts (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                title TEXT NOT NULL,
                body TEXT NOT NULL,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL
            )
            """
        )
        connection.execute(
            """
            CREATE TABLE IF NOT EXISTS inquiry_messages (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                email TEXT NOT NULL,
                sender_type TEXT NOT NULL,
                title TEXT NOT NULL,
                body TEXT NOT NULL,
                created_at TEXT NOT NULL
            )
            """
        )
        connection.commit()


def serialize_admin_user_row(row: sqlite3.Row) -> dict:
    return {
        "email": row["email"],
        "status": row["status"],
        "planName": row["plan_name"],
        "expiresAt": row["expires_at"],
        "deviceFingerprint": row["device_fingerprint"],
        "deviceBoundAt": row["device_bound_at"],
        "lastSeenAt": row["last_seen_at"],
        "notes": row["notes"],
        "createdAt": row["created_at"],
        "updatedAt": row["updated_at"],
    }


def load_admin_users() -> list[dict]:
    with get_db_connection() as connection:
        rows = connection.execute(
            """
            SELECT email, status, plan_name, expires_at, device_fingerprint,
                   device_bound_at, last_seen_at, notes, created_at, updated_at
            FROM admin_users
            ORDER BY updated_at DESC
            """
        ).fetchall()
    return [serialize_admin_user_row(row) for row in rows]


def find_admin_user(email: str) -> dict | None:
    normalized = str(email or "").strip().lower()
    if not normalized:
        return None

    with get_db_connection() as connection:
        row = connection.execute(
            """
            SELECT email, status, plan_name, expires_at, device_fingerprint,
                   device_bound_at, last_seen_at, notes, created_at, updated_at
            FROM admin_users
            WHERE email = ?
            """,
            (normalized,),
        ).fetchone()
    return serialize_admin_user_row(row) if row else None


def upsert_admin_user(email: str, updates: dict | None = None) -> dict:
    normalized = str(email or "").strip().lower()
    if not normalized:
        raise ValueError("이메일이 필요합니다.")

    now = datetime.now(timezone.utc).isoformat()
    existing = find_admin_user(normalized)

    user = {
        "email": normalized,
        "status": str((existing or {}).get("status") or "active"),
        "planName": str((existing or {}).get("planName") or "월구독형"),
        "expiresAt": str((existing or {}).get("expiresAt") or (datetime.now(timezone.utc) + timedelta(days=30)).isoformat()),
        "deviceFingerprint": str((existing or {}).get("deviceFingerprint") or ""),
        "deviceBoundAt": str((existing or {}).get("deviceBoundAt") or ""),
        "lastSeenAt": str((existing or {}).get("lastSeenAt") or ""),
        "notes": str((existing or {}).get("notes") or ""),
        "createdAt": str((existing or {}).get("createdAt") or now),
        "updatedAt": now,
    }

    if updates:
        user.update({key: value for key, value in updates.items() if value is not None})

    with get_db_connection() as connection:
        connection.execute(
            """
            INSERT INTO admin_users (
                email, status, plan_name, expires_at, device_fingerprint,
                device_bound_at, last_seen_at, notes, created_at, updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(email) DO UPDATE SET
                status=excluded.status,
                plan_name=excluded.plan_name,
                expires_at=excluded.expires_at,
                device_fingerprint=excluded.device_fingerprint,
                device_bound_at=excluded.device_bound_at,
                last_seen_at=excluded.last_seen_at,
                notes=excluded.notes,
                updated_at=excluded.updated_at
            """,
            (
                user["email"],
                user["status"],
                user["planName"],
                user["expiresAt"],
                user["deviceFingerprint"],
                user["deviceBoundAt"],
                user["lastSeenAt"],
                user["notes"],
                user["createdAt"],
                user["updatedAt"],
            ),
        )
        connection.commit()

    return find_admin_user(normalized) or user


def delete_admin_user(email: str) -> bool:
    normalized = str(email or "").strip().lower()
    if not normalized:
        raise ValueError("이메일이 필요합니다.")

    with get_db_connection() as connection:
        cursor = connection.execute(
            """
            DELETE FROM admin_users
            WHERE email = ?
            """,
            (normalized,),
        )
        connection.commit()
    return bool(cursor.rowcount)


def _normalize_message_type(message_type: str) -> str:
    normalized = str(message_type or "").strip().lower()
    if normalized not in {"notice", "reply"}:
        raise ValueError("message_type은 notice 또는 reply여야 합니다.")
    return normalized


def upsert_admin_message(
    message_type: str,
    title: str,
    body: str,
    *,
    target_email: str = "",
    is_active: bool = True,
) -> dict:
    normalized_type = _normalize_message_type(message_type)
    normalized_target = str(target_email or "").strip().lower()
    normalized_title = str(title or "").strip()
    normalized_body = str(body or "").strip()

    if normalized_type == "reply" and not normalized_target:
        raise ValueError("reply 저장 시 target_email이 필요합니다.")
    if normalized_type == "notice":
        normalized_target = ""
    if not normalized_title:
        raise ValueError("제목을 입력하세요.")
    if not normalized_body:
        raise ValueError("내용을 입력하세요.")

    now = datetime.now(timezone.utc).isoformat()
    with get_db_connection() as connection:
        existing = connection.execute(
            """
            SELECT created_at
            FROM admin_messages
            WHERE message_type = ? AND target_email = ?
            """,
            (normalized_type, normalized_target),
        ).fetchone()
        created_at = str(existing["created_at"]) if existing else now

        connection.execute(
            """
            INSERT INTO admin_messages (
                message_type, target_email, title, body, is_active, created_at, updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(message_type, target_email) DO UPDATE SET
                title=excluded.title,
                body=excluded.body,
                is_active=excluded.is_active,
                updated_at=excluded.updated_at
            """,
            (
                normalized_type,
                normalized_target,
                normalized_title,
                normalized_body,
                1 if is_active else 0,
                created_at,
                now,
            ),
        )
        connection.commit()

        row = connection.execute(
            """
            SELECT id, message_type, target_email, title, body, is_active, created_at, updated_at
            FROM admin_messages
            WHERE message_type = ? AND target_email = ?
            """,
            (normalized_type, normalized_target),
        ).fetchone()

    if not row:
        raise RuntimeError("메시지 저장 후 조회에 실패했습니다.")

    return {
        "id": row["id"],
        "messageType": row["message_type"],
        "targetEmail": row["target_email"],
        "title": row["title"],
        "body": row["body"],
        "isActive": bool(row["is_active"]),
        "createdAt": row["created_at"],
        "updatedAt": row["updated_at"],
    }


def get_user_messages(target_email: str = "") -> dict:
    normalized_target = str(target_email or "").strip().lower()
    with get_db_connection() as connection:
        notice_row = connection.execute(
            """
            SELECT id, message_type, target_email, title, body, is_active, created_at, updated_at
            FROM admin_messages
            WHERE message_type = 'notice' AND target_email = '' AND is_active = 1
            ORDER BY updated_at DESC
            LIMIT 1
            """
        ).fetchone()
        reply_row = None
        if normalized_target:
            reply_row = connection.execute(
                """
                SELECT id, message_type, target_email, title, body, is_active, created_at, updated_at
                FROM admin_messages
                WHERE message_type = 'reply' AND target_email = ? AND is_active = 1
                ORDER BY updated_at DESC
                LIMIT 1
                """,
                (normalized_target,),
            ).fetchone()

    def serialize(row: sqlite3.Row | None) -> dict | None:
        if not row:
            return None
        return {
            "id": row["id"],
            "messageType": row["message_type"],
            "targetEmail": row["target_email"],
            "title": row["title"],
            "body": row["body"],
            "isActive": bool(row["is_active"]),
            "createdAt": row["created_at"],
            "updatedAt": row["updated_at"],
        }

    return {
        "notice": serialize(notice_row),
        "reply": serialize(reply_row),
    }


def create_notice_post(title: str, body: str) -> dict:
    normalized_title = str(title or "").strip()
    normalized_body = str(body or "").strip()
    if not normalized_title:
        raise ValueError("공지 제목을 입력하세요.")
    if not normalized_body:
        raise ValueError("공지 내용을 입력하세요.")

    now = datetime.now(timezone.utc).isoformat()
    with get_db_connection() as connection:
        cursor = connection.execute(
            """
            INSERT INTO notice_posts (title, body, created_at, updated_at)
            VALUES (?, ?, ?, ?)
            """,
            (normalized_title, normalized_body, now, now),
        )
        connection.commit()
        row = connection.execute(
            """
            SELECT id, title, body, created_at, updated_at
            FROM notice_posts
            WHERE id = ?
            """,
            (cursor.lastrowid,),
        ).fetchone()

    if not row:
        raise RuntimeError("공지 저장 후 조회에 실패했습니다.")

    return {
        "id": row["id"],
        "title": row["title"],
        "body": row["body"],
        "createdAt": row["created_at"],
        "updatedAt": row["updated_at"],
    }


def list_notice_posts(limit: int = 20) -> list[dict]:
    safe_limit = max(1, min(int(limit or 20), 200))
    with get_db_connection() as connection:
        rows = connection.execute(
            """
            SELECT id, title, body, created_at, updated_at
            FROM notice_posts
            ORDER BY updated_at DESC
            LIMIT ?
            """,
            (safe_limit,),
        ).fetchall()
    return [
        {
            "id": row["id"],
            "title": row["title"],
            "body": row["body"],
            "createdAt": row["created_at"],
            "updatedAt": row["updated_at"],
        }
        for row in rows
    ]


def create_inquiry_message(email: str, sender_type: str, title: str, body: str) -> dict:
    normalized_email = str(email or "").strip().lower()
    normalized_sender = str(sender_type or "").strip().lower()
    normalized_title = str(title or "").strip()
    normalized_body = str(body or "").strip()

    if not normalized_email or "@" not in normalized_email:
        raise ValueError("올바른 이메일 주소가 필요합니다.")
    if normalized_sender not in {"user", "admin"}:
        raise ValueError("sender_type은 user 또는 admin이어야 합니다.")
    if not normalized_title:
        raise ValueError("제목을 입력하세요.")
    if not normalized_body:
        raise ValueError("내용을 입력하세요.")

    now = datetime.now(timezone.utc).isoformat()
    with get_db_connection() as connection:
        cursor = connection.execute(
            """
            INSERT INTO inquiry_messages (email, sender_type, title, body, created_at)
            VALUES (?, ?, ?, ?, ?)
            """,
            (normalized_email, normalized_sender, normalized_title, normalized_body, now),
        )
        connection.commit()
        row = connection.execute(
            """
            SELECT id, email, sender_type, title, body, created_at
            FROM inquiry_messages
            WHERE id = ?
            """,
            (cursor.lastrowid,),
        ).fetchone()

    if not row:
        raise RuntimeError("문의 저장 후 조회에 실패했습니다.")

    return {
        "id": row["id"],
        "email": row["email"],
        "senderType": row["sender_type"],
        "title": row["title"],
        "body": row["body"],
        "createdAt": row["created_at"],
    }


def list_inquiry_messages(target_email: str = "", limit: int = 100) -> list[dict]:
    normalized_email = str(target_email or "").strip().lower()
    safe_limit = max(1, min(int(limit or 100), 300))
    with get_db_connection() as connection:
        if normalized_email:
            rows = connection.execute(
                """
                SELECT id, email, sender_type, title, body, created_at
                FROM inquiry_messages
                WHERE email = ?
                ORDER BY created_at DESC
                LIMIT ?
                """,
                (normalized_email, safe_limit),
            ).fetchall()
        else:
            rows = connection.execute(
                """
                SELECT id, email, sender_type, title, body, created_at
                FROM inquiry_messages
                ORDER BY created_at DESC
                LIMIT ?
                """,
                (safe_limit,),
            ).fetchall()

    return [
        {
            "id": row["id"],
            "email": row["email"],
            "senderType": row["sender_type"],
            "title": row["title"],
            "body": row["body"],
            "createdAt": row["created_at"],
        }
        for row in rows
    ]


def get_latest_user_message_bundle(target_email: str = "") -> dict:
    normalized_email = str(target_email or "").strip().lower()
    notices = list_notice_posts(limit=1)
    latest_notice = notices[0] if notices else None

    latest_reply = None
    if normalized_email:
        messages = list_inquiry_messages(normalized_email, limit=30)
        latest_reply = next((item for item in messages if item.get("senderType") == "admin"), None)

    if latest_notice is None:
        fallback = get_user_messages(normalized_email)
        latest_notice = fallback.get("notice")
        if latest_reply is None:
            latest_reply = fallback.get("reply")

    return {
        "notice": latest_notice,
        "reply": latest_reply,
    }


def mask_secret(value: str, *, keep_start: int = 6, keep_end: int = 4) -> str:
    value = str(value or "").strip()
    if not value:
        return ""
    if len(value) <= keep_start + keep_end:
        return "*" * len(value)
    return f"{value[:keep_start]}{'*' * max(len(value) - keep_start - keep_end, 4)}{value[-keep_end:]}"


def get_machine_fingerprint() -> str:
    raw = "|".join(
        [
            platform.node(),
            os.environ.get("COMPUTERNAME", ""),
            os.environ.get("USERNAME", ""),
            platform.platform(),
        ]
    )
    return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:16]


def build_license_status(config: dict | None = None) -> dict:
    config = config or load_app_config()
    saved_status = config.get("licenseStatus") if isinstance(config.get("licenseStatus"), dict) else {}
    account_email = str(config.get("accountEmail") or "").strip()
    auth_token = str(config.get("authToken") or "").strip()
    account_status = str(config.get("accountStatus") or "pending").strip() or "pending"
    expires_at = str(config.get("expiresAt") or "").strip()
    plan_name = str(config.get("planName") or "").strip()

    base_status = {
        "status": "unconfigured",
        "message": "대시보드에서 이메일을 1회 입력해 기기를 등록하세요.",
        "checkedAt": "",
        "expiresAt": expires_at,
        "licensee": account_email,
        "planName": plan_name,
        "serverReachable": False,
        "machineId": get_machine_fingerprint(),
        "deviceBound": False,
        "authTokenMasked": mask_secret(auth_token),
        "appVersion": APP_VERSION,
        "mode": "local_stub",
        "accountStatus": account_status,
    }

    if not account_email:
        return base_status

    if saved_status:
        merged = {**base_status, **saved_status}
        # Keep dynamic fields in sync with the latest config values.
        merged["expiresAt"] = expires_at
        merged["licensee"] = account_email
        merged["planName"] = plan_name
        merged["accountStatus"] = account_status
        merged["machineId"] = get_machine_fingerprint()
        merged["authTokenMasked"] = mask_secret(auth_token)
        merged["appVersion"] = APP_VERSION
        merged["mode"] = "local_stub"
        return merged

    return {
        **base_status,
        "status": "pending",
        "message": "이메일 연결은 저장되었지만 아직 기기 검증을 하지 않았습니다.",
    }


def perform_license_check(config: dict) -> dict:
    account_email = str(config.get("accountEmail") or "").strip()
    auth_token = str(config.get("authToken") or "").strip()
    bound_fingerprint = str(config.get("boundFingerprint") or "").strip()
    current_fingerprint = get_machine_fingerprint()
    admin_user = find_admin_user(account_email) if account_email else None
    if admin_user:
        config["accountStatus"] = str(admin_user.get("status") or config.get("accountStatus") or "active")
        config["expiresAt"] = str(admin_user.get("expiresAt") or config.get("expiresAt") or "")
        config["planName"] = str(admin_user.get("planName") or config.get("planName") or "월구독형")
        if str(admin_user.get("deviceFingerprint") or "").strip():
            config["boundFingerprint"] = str(admin_user.get("deviceFingerprint") or "").strip()
            bound_fingerprint = str(config.get("boundFingerprint") or "").strip()
        save_app_config(config)

    account_status = str(config.get("accountStatus") or "active").strip() or "active"
    expires_at_raw = str(config.get("expiresAt") or "").strip()
    plan_name = str(config.get("planName") or "월구독형").strip() or "월구독형"
    now = datetime.now(timezone.utc)

    if not account_email or not auth_token:
        return {
            "status": "unconfigured",
            "message": "먼저 이메일로 로그인해 현재 PC를 등록하세요.",
            "checkedAt": now.isoformat(),
            "expiresAt": "",
            "licensee": account_email,
            "planName": plan_name,
            "serverReachable": False,
            "deviceBound": False,
            "accountStatus": "pending",
        }

    if not bound_fingerprint:
        return {
            "status": "pending",
            "message": "기기 바인딩 정보가 없어 다시 로그인해야 합니다.",
            "checkedAt": now.isoformat(),
            "expiresAt": "",
            "licensee": account_email,
            "planName": plan_name,
            "serverReachable": True,
            "deviceBound": False,
            "accountStatus": account_status,
        }

    expires_at = now + timedelta(days=30)
    if expires_at_raw:
        try:
            expires_at = datetime.fromisoformat(expires_at_raw.replace("Z", "+00:00"))
        except ValueError:
            expires_at = now + timedelta(days=30)

    if expires_at <= now:
        return {
            "status": "error",
            "message": "사용 기간이 만료되었습니다. 운영자에게 연장 요청이 필요합니다.",
            "checkedAt": now.isoformat(),
            "expiresAt": expires_at.isoformat(),
            "licensee": account_email,
            "planName": plan_name,
            "serverReachable": True,
            "deviceBound": True,
            "accountStatus": "expired",
        }

    if account_status != "active":
        return {
            "status": "error",
            "message": "현재 계정 상태가 활성화되어 있지 않습니다.",
            "checkedAt": now.isoformat(),
            "expiresAt": expires_at.isoformat(),
            "licensee": account_email,
            "planName": plan_name,
            "serverReachable": True,
            "deviceBound": True,
            "accountStatus": account_status,
        }

    if bound_fingerprint != current_fingerprint:
        return {
            "status": "error",
            "message": "등록된 PC 정보와 현재 기기 정보가 다릅니다. 1계정 1PC 정책으로 실행이 제한됩니다.",
            "checkedAt": now.isoformat(),
            "expiresAt": expires_at.isoformat(),
            "licensee": account_email,
            "planName": plan_name,
            "serverReachable": True,
            "deviceBound": True,
            "accountStatus": account_status,
        }

    return {
        "status": "active",
        "message": "저장된 계정과 현재 PC fingerprint가 일치합니다.",
        "checkedAt": now.isoformat(),
        "expiresAt": expires_at.isoformat(),
        "licensee": account_email,
        "planName": plan_name,
        "serverReachable": True,
        "deviceBound": True,
        "accountStatus": account_status,
    }


def update_license_status(config: dict, status: dict) -> dict:
    merged_status = {
        **status,
        "machineId": get_machine_fingerprint(),
        "authTokenMasked": mask_secret(str(config.get("authToken") or "")),
        "appVersion": APP_VERSION,
        "mode": "local_stub",
    }
    config["licenseStatus"] = merged_status
    save_app_config(config)
    return merged_status


def perform_email_login(email: str) -> tuple[dict, dict]:
    normalized_email = str(email or "").strip().lower()
    if not normalized_email or "@" not in normalized_email:
        raise ValueError("올바른 이메일 주소를 입력하세요.")

    machine_id = get_machine_fingerprint()
    config = load_app_config()
    existing_email = str(config.get("accountEmail") or "").strip().lower()
    existing_fingerprint = str(config.get("boundFingerprint") or "").strip()

    if existing_email and existing_email != normalized_email:
        raise RuntimeError("이미 다른 이메일이 이 PC에 연결되어 있습니다. 계정 변경 정책이 필요합니다.")

    if existing_fingerprint and existing_fingerprint != machine_id:
        raise RuntimeError("이미 다른 PC에 연결된 토큰입니다.")

    current_time = datetime.now(timezone.utc).isoformat()
    admin_user = upsert_admin_user(
        normalized_email,
        {
            "deviceFingerprint": machine_id,
            "deviceBoundAt": current_time,
            "lastSeenAt": current_time,
        },
    )

    config["accountEmail"] = normalized_email
    config["authToken"] = str(config.get("authToken") or secrets.token_hex(16))
    config["boundFingerprint"] = machine_id
    config["boundAt"] = str(admin_user.get("deviceBoundAt") or current_time)
    config["accountStatus"] = str(admin_user.get("status") or "active")
    config["expiresAt"] = str(admin_user.get("expiresAt") or (datetime.now(timezone.utc) + timedelta(days=30)).isoformat())
    config["planName"] = str(admin_user.get("planName") or "월구독형")

    status = update_license_status(
        config,
        {
            "status": "active",
            "message": "이메일 1회 로그인 후 현재 PC가 계정에 연결되었습니다.",
            "checkedAt": current_time,
            "expiresAt": str(config.get("expiresAt") or ""),
            "licensee": normalized_email,
            "planName": str(config.get("planName") or "월구독형"),
            "serverReachable": True,
            "deviceBound": True,
            "accountStatus": str(config.get("accountStatus") or "active"),
        },
    )

    return config, status
