"""Minimal NexaOffice server bootstrap.

This is a safe starting point for future online deployment. It is intentionally
small: health check, authenticated snapshot receive, and workspace echo. Replace
JSON-file storage with a real database before production multi-user use.
"""
from __future__ import annotations

from datetime import datetime
import json
import os
from pathlib import Path
from typing import Any

from fastapi import FastAPI, Header, HTTPException, Request
from pydantic import BaseModel, Field

APP_DATA = Path(os.environ.get("NEXA_SERVER_DATA", "./server_data"))
APP_DATA.mkdir(parents=True, exist_ok=True)
DEPLOYMENT_TOKEN = os.environ.get("NEXA_DEPLOYMENT_TOKEN", "")
DEFAULT_WORKSPACE_ID = os.environ.get("NEXA_WORKSPACE_ID", "default")

app = FastAPI(title="NexaOffice Server", version="2026.06.20.80")


class SyncEnvelope(BaseModel):
    entity: str = Field(default="workspace_snapshot")
    action: str = Field(default="upsert")
    local_id: str = Field(default="workspace")
    payload: dict[str, Any] = Field(default_factory=dict)
    client_time: str | None = None


class ContactRegister(BaseModel):
    display_name: str = Field(default="")
    account: str = Field(default="")
    phone: str = Field(default="")
    email: str = Field(default="")
    public_contact_code: str = Field(default="")
    internal_id: str = Field(default="")


class ContactResolveRequest(BaseModel):
    method: str = Field(default="")
    value: str = Field(default="")


class BootstrapRequest(BaseModel):
    client_version: str = Field(default="")
    account: str = Field(default="")
    scope: str = Field(default="personal")
    workspace_puid: str = Field(default="")
    capabilities: list[str] = Field(default_factory=list)


class SyncBatchEnvelope(BaseModel):
    scope: str = Field(default="personal")
    account: str = Field(default="")
    items: list[SyncEnvelope] = Field(default_factory=list)


def _scope_value(scope: str) -> str:
    scope = str(scope or "personal").strip().lower()
    return scope if scope in {"personal", "enterprise"} else "personal"


def _contacts_path(workspace_id: str) -> Path:
    return APP_DATA / f"{workspace_id}_contacts.json"


def _load_contacts(workspace_id: str) -> list[dict[str, Any]]:
    path = _contacts_path(workspace_id)
    if not path.exists():
        return []
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
        return data if isinstance(data, list) else []
    except Exception:
        return []


def _save_contacts(workspace_id: str, contacts: list[dict[str, Any]]) -> None:
    path = _contacts_path(workspace_id)
    path.write_text(json.dumps(contacts, ensure_ascii=False, indent=2), encoding="utf-8")


def _public_contact(contact: dict[str, Any]) -> dict[str, Any]:
    # Never expose internal IDs or server-only link fields in contact lookup responses.
    return {
        "display_name": contact.get("display_name", ""),
        "account": contact.get("account", ""),
        "phone": contact.get("phone", ""),
        "email": contact.get("email", ""),
        "public_contact_code": contact.get("public_contact_code", ""),
    }


def _check_auth(authorization: str | None, workspace_id: str | None) -> str:
    if DEPLOYMENT_TOKEN:
        expected = f"Bearer {DEPLOYMENT_TOKEN}"
        if authorization != expected:
            raise HTTPException(status_code=401, detail="Unauthorized")
    return workspace_id or DEFAULT_WORKSPACE_ID


@app.get("/health")
def health() -> dict[str, Any]:
    return {
        "ok": True,
        "service": "nexaoffice-server",
        "time": datetime.utcnow().isoformat(timespec="seconds") + "Z",
    }


@app.get("/api/health")
def api_health() -> dict[str, Any]:
    return health()


@app.post("/api/bootstrap")
def bootstrap_client(
    item: BootstrapRequest,
    authorization: str | None = Header(default=None),
    x_workspace_id: str | None = Header(default=None),
) -> dict[str, Any]:
    workspace_id = _check_auth(authorization, x_workspace_id)
    scope = _scope_value(item.scope)
    return {
        "ok": True,
        "workspace_id": workspace_id,
        "server_time": datetime.utcnow().isoformat(timespec="seconds") + "Z",
        "contract": "v2026.06.20.80-scope-isolated",
        "supported_scopes": ["personal", "enterprise"],
        "active_scope": scope,
        "token_displayed": False,
        "required_client_fields": ["scope", "account", "workspace_puid", "entity", "action", "local_id"],
        "limits": {"batch_items": 100, "payload_chars": 12000},
    }


@app.post("/api/sync")
async def receive_sync(
    item: SyncEnvelope,
    request: Request,
    authorization: str | None = Header(default=None),
    x_workspace_id: str | None = Header(default=None),
) -> dict[str, Any]:
    workspace_id = _check_auth(authorization, x_workspace_id)
    target = APP_DATA / f"{workspace_id}.jsonl"
    scope = _scope_value(item.payload.get("scope") if isinstance(item.payload, dict) else "personal")
    record = {
        "received_at": datetime.utcnow().isoformat(timespec="seconds") + "Z",
        "client": request.client.host if request.client else "unknown",
        "scope": scope,
        "entity": item.entity,
        "action": item.action,
        "local_id": item.local_id,
        "payload": item.payload,
        "client_time": item.client_time,
    }
    with target.open("a", encoding="utf-8") as f:
        f.write(json.dumps(record, ensure_ascii=False, separators=(",", ":")) + "\n")
    return {"ok": True, "workspace_id": workspace_id, "stored": True, "scope": scope}


@app.post("/api/sync/batch")
async def receive_sync_batch(
    batch: SyncBatchEnvelope,
    request: Request,
    authorization: str | None = Header(default=None),
    x_workspace_id: str | None = Header(default=None),
) -> dict[str, Any]:
    workspace_id = _check_auth(authorization, x_workspace_id)
    scope = _scope_value(batch.scope)
    items = list(batch.items or [])[:100]
    target = APP_DATA / f"{workspace_id}_{scope}_batch.jsonl"
    received_at = datetime.utcnow().isoformat(timespec="seconds") + "Z"
    stored = 0
    with target.open("a", encoding="utf-8") as f:
        for item in items:
            record = {
                "received_at": received_at,
                "client": request.client.host if request.client else "unknown",
                "scope": scope,
                "account": batch.account,
                "entity": item.entity,
                "action": item.action,
                "local_id": item.local_id,
                "payload": item.payload,
                "client_time": item.client_time,
            }
            f.write(json.dumps(record, ensure_ascii=False, separators=(",", ":")) + "\n")
            stored += 1
    return {"ok": True, "workspace_id": workspace_id, "scope": scope, "stored": stored, "accepted": len(items)}


@app.post("/api/contacts/register")
def register_contact(
    item: ContactRegister,
    authorization: str | None = Header(default=None),
    x_workspace_id: str | None = Header(default=None),
) -> dict[str, Any]:
    workspace_id = _check_auth(authorization, x_workspace_id)
    contacts = _load_contacts(workspace_id)
    public_code = item.public_contact_code.strip()
    account = item.account.strip()
    email = item.email.strip().lower()
    phone = item.phone.strip()
    existing_index = -1
    for idx, contact in enumerate(contacts):
        if public_code and contact.get("public_contact_code") == public_code:
            existing_index = idx
            break
        if account and contact.get("account") == account:
            existing_index = idx
            break
        if email and str(contact.get("email", "")).lower() == email:
            existing_index = idx
            break
        if phone and contact.get("phone") == phone:
            existing_index = idx
            break
    record = {
        "display_name": item.display_name.strip(),
        "account": account,
        "phone": phone,
        "email": item.email.strip(),
        "public_contact_code": public_code,
        "internal_id": item.internal_id.strip(),
        "updated_at": datetime.utcnow().isoformat(timespec="seconds") + "Z",
    }
    if existing_index >= 0:
        contacts[existing_index].update(record)
    else:
        contacts.append(record)
    _save_contacts(workspace_id, contacts)
    return {"ok": True, "workspace_id": workspace_id, "contact": _public_contact(record)}


@app.post("/api/contacts/resolve")
def resolve_contact(
    query: ContactResolveRequest,
    authorization: str | None = Header(default=None),
    x_workspace_id: str | None = Header(default=None),
) -> dict[str, Any]:
    workspace_id = _check_auth(authorization, x_workspace_id)
    method = query.method.strip().lower()
    value = query.value.strip()
    value_lower = value.lower()
    if not value:
        raise HTTPException(status_code=400, detail="Missing Contact Value")
    for contact in _load_contacts(workspace_id):
        candidates = {
            "phone": contact.get("phone", ""),
            "email": str(contact.get("email", "")).lower(),
            "account": contact.get("account", ""),
            "qr": contact.get("public_contact_code", ""),
            "code": contact.get("public_contact_code", ""),
        }
        if method in candidates:
            matched = (candidates[method].lower() if isinstance(candidates[method], str) else candidates[method]) == value_lower
        else:
            matched = value_lower in [str(v).lower() for v in candidates.values() if v]
        if matched:
            return {"ok": True, "workspace_id": workspace_id, "found": True, "contact": _public_contact(contact)}
    return {"ok": True, "workspace_id": workspace_id, "found": False, "contact": None}
