"""A tenant's connected Google Calendar account: reading it, refreshing its
access token when expired, and everything below this module needs to call
the Calendar API.

The connection is written by the dashboard's generic OAuth connect flow
into the tenant's own schema -- the same galaxiq_tenants database and
per-tenant `integrations` table mailchimp.py's connection lives beside (see
app/services/infra/database.py's get_setting). That connect flow lives in a
different service, not this one; this module only ever reads what it wrote,
and writes back a refreshed access token in place.
"""
import logging
from datetime import datetime, timedelta, timezone

import httpx
from psycopg2 import sql

from app.core.config import settings
from app.services.infra.database import get_db_connection

logger = logging.getLogger(__name__)

TOKEN_URL = "https://oauth2.googleapis.com/token"
CALENDAR_URL = "https://www.googleapis.com/calendar/v3/calendars/primary"

PROVIDER = "google-calendar"


def get_google_calendar_connection(tenant_id: str):
    """This tenant's active Google Calendar connection, refreshed if its
    access token has expired.

    Returns {"access_token", "calendar_email", "timezone"} -- everything
    check_availability/create_event/cancel_event need. None covers every
    reason it cannot be used: no connection, a disconnected one, or a
    refresh failure (revoked consent).
    """
    try:
        conn = get_db_connection()
    except Exception as ex:
        logger.error(f"Could not read the Google Calendar connection for {tenant_id}: {ex}")
        return None

    try:
        with conn.cursor() as cur:
            cur.execute(
                sql.SQL('SELECT "accessToken", "refreshToken", "expiresAt" FROM '
                        '{}.integrations WHERE provider = %s AND status = %s')
                .format(sql.Identifier(tenant_id)),
                (PROVIDER, "ACTIVE"))
            row = cur.fetchone()
    except Exception as ex:
        logger.error(f"Could not read the Google Calendar connection for {tenant_id}: {ex}")
        return None
    finally:
        conn.close()

    if not row:
        return None

    access_token, refresh_token, expires_at = row
    if expires_at is not None and expires_at <= datetime.now(timezone.utc):
        access_token = _refresh(tenant_id, refresh_token)
        if not access_token:
            return None

    try:
        response = httpx.get(
            CALENDAR_URL,
            headers={"Authorization": f"Bearer {access_token}"},
            timeout=20,
        )
        response.raise_for_status()
        calendar = response.json()
    except Exception as ex:
        logger.error(f"Could not read the Google Calendar for {tenant_id}: {ex}")
        return None

    return {
        "access_token": access_token,
        "calendar_email": calendar.get("id") or "",
        "timezone": calendar.get("timeZone") or "UTC",
    }


def _refresh(tenant_id: str, refresh_token: str):
    """Exchanges a refresh token for a new access token, and writes the new
    token + expiry back onto the same row so the next call doesn't refresh
    again. Returns the new access token, or None on failure (a revoked
    refresh token is the same as never having connected)."""
    try:
        response = httpx.post(
            TOKEN_URL,
            data={
                "client_id": settings.GOOGLE_CALENDAR_CLIENT_ID,
                "client_secret": settings.GOOGLE_CALENDAR_CLIENT_SECRET,
                "refresh_token": refresh_token,
                "grant_type": "refresh_token",
            },
            timeout=20,
        )
        response.raise_for_status()
        payload = response.json()
    except Exception as ex:
        logger.error(f"Could not refresh the Google Calendar token for {tenant_id}: {ex}")
        return None

    access_token = payload.get("access_token")
    if not access_token:
        return None

    expires_at = datetime.now(timezone.utc) + timedelta(
        seconds=payload.get("expires_in") or 3600)

    try:
        conn = get_db_connection()
    except Exception as ex:
        logger.error(f"Could not persist the refreshed Google Calendar token "
                     f"for {tenant_id}: {ex}")
        return access_token

    try:
        with conn.cursor() as cur:
            cur.execute(
                sql.SQL('UPDATE {}.integrations SET "accessToken" = %s, '
                        '"expiresAt" = %s WHERE provider = %s')
                .format(sql.Identifier(tenant_id)),
                (access_token, expires_at, PROVIDER))
        conn.commit()
    except Exception as ex:
        conn.rollback()
        logger.error(f"Could not persist the refreshed Google Calendar token "
                     f"for {tenant_id}: {ex}")
    finally:
        conn.close()

    return access_token


FREEBUSY_URL = "https://www.googleapis.com/calendar/v3/freeBusy"
EVENTS_URL = "https://www.googleapis.com/calendar/v3/calendars/primary/events"

SLOT_MINUTES = 30


def check_availability(connection: dict, start, end, duration_minutes: int = SLOT_MINUTES) -> list:
    """Open duration_minutes-long slots inside [start, end], starting at
    `start` and stepping forward every duration_minutes -- slots land on
    the half hour only when the caller passes a midnight- or
    half-hour-aligned `start`, e.g. a business-timezone day boundary.
    Returns [] on any API failure -- an empty list of slots reads to
    the model as "nothing available", which is the safe failure mode
    (never claims a slot is free when the check itself failed)."""
    try:
        response = httpx.post(
            FREEBUSY_URL,
            headers={"Authorization": f"Bearer {connection['access_token']}"},
            json={
                "timeMin": start.isoformat(),
                "timeMax": end.isoformat(),
                "items": [{"id": connection["calendar_email"]}],
            },
            timeout=20,
        )
        response.raise_for_status()
        busy_blocks = response.json()["calendars"][connection["calendar_email"]]["busy"]

        busy = [(datetime.fromisoformat(b["start"]), datetime.fromisoformat(b["end"]))
                for b in busy_blocks]

        slots = []
        cursor = start
        step = timedelta(minutes=duration_minutes)
        while cursor + step <= end:
            slot_end = cursor + step
            if not any(cursor < b_end and slot_end > b_start for b_start, b_end in busy):
                slots.append({"start": cursor, "end": slot_end})
            cursor += step
        return slots
    except Exception as ex:
        logger.error(f"Could not check Google Calendar availability: {ex}")
        return []


def create_event(connection: dict, start, end, visitor_email: str, visitor_name: str):
    """Books the slot and emails the visitor a calendar invite (sendUpdates
    'all' is what makes Calendar actually send it) with an auto-attached
    Meet link (conferenceDataVersion 1). Returns None on failure -- never
    raises, matching every other provider call in this module."""
    try:
        response = httpx.post(
            EVENTS_URL,
            headers={"Authorization": f"Bearer {connection['access_token']}"},
            params={"sendUpdates": "all", "conferenceDataVersion": 1},
            json={
                "summary": f"Meeting with {visitor_name}",
                "start": {"dateTime": start.isoformat()},
                "end": {"dateTime": end.isoformat()},
                "attendees": [{"email": visitor_email}],
                "conferenceData": {
                    "createRequest": {"requestId": f"{visitor_email}-{start.isoformat()}"}},
            },
            timeout=20,
        )
        response.raise_for_status()
        event = response.json()
        event_id = event.get("id")
        if not event_id:
            logger.error("Could not create the Google Calendar event: response missing 'id'")
            return None
        return {"event_id": event_id, "meeting_link": event.get("hangoutLink")}
    except Exception as ex:
        logger.error(f"Could not create the Google Calendar event: {ex}")
        return None


def cancel_event(connection: dict, visitor_email: str, near_time=None) -> bool:
    """Finds the event by attendee email (narrowed to a window around
    near_time when given) and deletes it. Returns False -- does not guess
    -- when zero or more than one event matches.

    The `q` full-text search only narrows candidates -- it also matches an
    event that merely mentions the email in its summary/description text
    without the visitor actually being an attendee. The final match is
    always confirmed client-side against each candidate's `attendees` list.
    """
    params = {"q": visitor_email}
    if near_time is not None:
        params["timeMin"] = (near_time - timedelta(hours=2)).isoformat()
        params["timeMax"] = (near_time + timedelta(hours=2)).isoformat()

    try:
        response = httpx.get(
            EVENTS_URL,
            headers={"Authorization": f"Bearer {connection['access_token']}"},
            params=params,
            timeout=20,
        )
        response.raise_for_status()
        candidates = response.json().get("items") or []

        # Kept inside this try/except -- a malformed provider response (e.g.
        # `attendees` is null, or an entry isn't a dict) must fail the same
        # "never raise, return False" way the HTTP call and JSON parsing
        # above already do, not crash the whole chat turn.
        visitor_email_lower = visitor_email.lower()
        items = []
        for event in candidates:
            if not isinstance(event, dict):
                continue
            attendees = event.get("attendees")
            if not isinstance(attendees, list):
                continue
            if any(isinstance(attendee, dict)
                   and (attendee.get("email") or "").lower() == visitor_email_lower
                   for attendee in attendees):
                items.append(event)
    except Exception as ex:
        logger.error(f"Could not search the Google Calendar for cancellation: {ex}")
        return False

    if len(items) != 1:
        return False

    try:
        response = httpx.delete(
            f"{EVENTS_URL}/{items[0]['id']}",
            headers={"Authorization": f"Bearer {connection['access_token']}"},
            timeout=20,
        )
        response.raise_for_status()
        return True
    except Exception as ex:
        logger.error(f"Could not delete the Google Calendar event: {ex}")
        return False
