from datetime import datetime, timezone
from fastapi import Depends, FastAPI, Query
from sqlalchemy import select
from sqlalchemy.orm import Session
from .models import Contact, SessionLocal, init_db, now_utc
from .schemas import ContactOut, DirectoryResponse
from .security import require_api_key
from .threecx import ThreeCXClient, normalize_user

app = FastAPI(title='3CX Directory Sync API')


@app.on_event('startup')
def startup() -> None:
    init_db()


def db_session():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()


def to_out(contact: Contact) -> ContactOut:
    return ContactOut(
        id=contact.id,
        extension=contact.extension,
        firstName=contact.first_name,
        lastName=contact.last_name,
        displayName=contact.display_name,
        email=contact.email,
        mobile=contact.mobile,
        directNumber=contact.direct_number,
        department=contact.department,
        jobTitle=contact.job_title,
        deleted=contact.deleted,
        updatedAt=contact.updated_at,
    )


@app.get('/health')
def health() -> dict[str, str]:
    return {'status': 'ok'}


@app.post('/sync/3cx', dependencies=[Depends(require_api_key)])
async def sync_3cx(db: Session = Depends(db_session)) -> dict[str, int]:
    client = ThreeCXClient()
    users = await client.users()
    seen_ids: set[str] = set()
    changed = 0
    sync_time = now_utc()

    for raw in users:
        item = normalize_user(raw)
        if not item['id']:
            continue
        seen_ids.add(item['id'])
        existing = db.get(Contact, item['id'])
        if existing is None:
            db.add(Contact(**item, deleted=False, updated_at=sync_time))
            changed += 1
        else:
            dirty = False
            for key, value in item.items():
                if getattr(existing, key) != value:
                    setattr(existing, key, value)
                    dirty = True
            if existing.deleted:
                existing.deleted = False
                dirty = True
            if dirty:
                existing.updated_at = sync_time
                changed += 1

    for existing in db.scalars(select(Contact).where(Contact.deleted.is_(False))).all():
        if existing.id not in seen_ids:
            existing.deleted = True
            existing.updated_at = sync_time
            changed += 1

    db.commit()
    return {'received': len(users), 'changed': changed}


@app.get('/directory/full', response_model=DirectoryResponse, dependencies=[Depends(require_api_key)])
def directory_full(db: Session = Depends(db_session)) -> DirectoryResponse:
    contacts = db.scalars(select(Contact).order_by(Contact.display_name)).all()
    return DirectoryResponse(serverTime=datetime.now(timezone.utc), contacts=[to_out(c) for c in contacts])


@app.get('/directory/changes', response_model=DirectoryResponse, dependencies=[Depends(require_api_key)])
def directory_changes(since: datetime = Query(...), db: Session = Depends(db_session)) -> DirectoryResponse:
    contacts = db.scalars(select(Contact).where(Contact.updated_at > since).order_by(Contact.updated_at)).all()
    return DirectoryResponse(serverTime=datetime.now(timezone.utc), contacts=[to_out(c) for c in contacts])
