"""
Google Analytics 4 Data API helpers for the CMS dashboard.
Requires: pip install google-analytics-data
"""
import json
from datetime import datetime
from django.conf import settings
from django.core.cache import cache

GA_CACHE_KEY = 'ga_dashboard_data_{days}'
GA_CACHE_TTL = 1800  # 30 minutes

_GA_EMPTY = {
    'ga_ok': False,
    'ga_error': None,
    'total_users': 0, 'total_sessions': 0,
    'total_pageviews': 0, 'bounce_rate': 0,
    'avg_duration': '—',
    'ga_trend_labels': '[]', 'trend_sessions': '[]', 'trend_users': '[]',
    'top_pages': [],
    'channel_labels': '[]', 'channel_data': '[]',
    'device_labels': '[]',  'device_data': '[]',
    'referral_labels': '[]', 'referral_data': '[]',
    'country_labels': '[]',  'country_data': '[]',
}


def _client(tenant=None):
    """
    An Analytics client built from the service account stored in Settings.

    This used to read a file named by GA_CREDENTIALS_FILE. On the Mac that
    path pointed at a folder layout abandoned months ago, so the file was not
    there and Analytics failed silently. Google accepts the same document as a
    dict, so the key now lives encrypted in the database and never touches
    disk.
    """
    from google.analytics.data_v1beta import BetaAnalyticsDataClient
    from google.oauth2 import service_account
    from apps.pages.api_keys import _site

    site = _site(tenant)
    raw = (getattr(site, 'ga_credentials_json', '') or '').strip() if site else ''
    if not raw:
        raise RuntimeError('No Google service account saved. Add it under Settings.')

    # Copying a JSON file out of a terminal and into a textarea drops the
    # first line more often than you would think, and the failure is a
    # decoder error about column 7 that says nothing about a missing brace.
    # Put back what is obviously missing, then let the parser judge.
    if not raw.startswith('{'):
        raw = '{' + raw
    if not raw.endswith('}'):
        raw = raw + '}'

    try:
        info = json.loads(raw)
    except ValueError as exc:
        raise RuntimeError(
            'The Google service account in Settings is not valid JSON. '
            'Paste the whole file, opening brace to closing brace. '
            f'({exc})'
        )

    missing = [k for k in ('private_key', 'client_email', 'token_uri') if not info.get(k)]
    if missing:
        raise RuntimeError(
            'That does not look like a service account file. Missing: '
            + ', '.join(missing)
        )

    creds = service_account.Credentials.from_service_account_info(
        info,
        scopes=['https://www.googleapis.com/auth/analytics.readonly'],
    )
    return BetaAnalyticsDataClient(credentials=creds)


def _prop():
    # This used to default to one particular site's property ID in base.py and
    # production.py. An install with no value of its own therefore read that
    # site's traffic and showed it as its own, and nothing anywhere said so.
    # Blank now, and a blank one stops here with a message rather than asking
    # Google for "properties/".
    pid = (getattr(settings, 'GA_PROPERTY_ID', '') or '').strip()
    if not pid:
        raise RuntimeError(
            'No Google Analytics property set. Add GA_PROPERTY_ID to .env, '
            'from Analytics Admin, then restart.'
        )
    return f"properties/{pid}"


def _do_fetch(days):
    """All GA API calls. Runs inside a thread so it can be hard-killed on timeout."""
    from google.analytics.data_v1beta.types import (
        RunReportRequest, DateRange, Metric, Dimension, OrderBy,
    )
    client = _client()
    dr = [DateRange(start_date=f'{days}daysAgo', end_date='today')]

    # 1. Summary totals
    r0 = client.run_report(RunReportRequest(
        property=_prop(), date_ranges=dr,
        metrics=[
            Metric(name='activeUsers'), Metric(name='sessions'),
            Metric(name='screenPageViews'), Metric(name='bounceRate'),
            Metric(name='averageSessionDuration'),
        ],
    ), timeout=6)
    row = r0.rows[0].metric_values if r0.rows else []
    total_users     = int(row[0].value)                    if row else 0
    total_sessions  = int(row[1].value)                    if row else 0
    total_pageviews = int(row[2].value)                    if row else 0
    bounce_rate     = round(float(row[3].value) * 100, 1)  if row else 0
    avg_sec         = int(float(row[4].value))             if row else 0
    avg_duration    = f"{avg_sec // 60}m {avg_sec % 60}s"

    # 2. Trend (sessions + users over time)
    r1 = client.run_report(RunReportRequest(
        property=_prop(), date_ranges=dr,
        dimensions=[Dimension(name='date')],
        metrics=[Metric(name='sessions'), Metric(name='activeUsers')],
        order_bys=[OrderBy(dimension=OrderBy.DimensionOrderBy(dimension_name='date'))],
    ), timeout=6)
    trend_labels, trend_sessions, trend_users = [], [], []
    for r in r1.rows:
        dt = datetime.strptime(r.dimension_values[0].value, '%Y%m%d')
        trend_labels.append(dt.strftime('%-d %b'))
        trend_sessions.append(int(r.metric_values[0].value))
        trend_users.append(int(r.metric_values[1].value))

    # 3. Top pages
    r2 = client.run_report(RunReportRequest(
        property=_prop(), date_ranges=dr,
        dimensions=[Dimension(name='pagePath')],
        metrics=[Metric(name='screenPageViews'), Metric(name='activeUsers')],
        order_bys=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name='screenPageViews'), desc=True)],
        limit=8,
    ), timeout=6)
    top_pages = [{'path': r.dimension_values[0].value,
                  'views': int(r.metric_values[0].value),
                  'users': int(r.metric_values[1].value)} for r in r2.rows]

    # 4. Channels
    r3 = client.run_report(RunReportRequest(
        property=_prop(), date_ranges=dr,
        dimensions=[Dimension(name='sessionDefaultChannelGroup')],
        metrics=[Metric(name='sessions')],
        order_bys=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name='sessions'), desc=True)],
        limit=6,
    ), timeout=6)
    channel_labels = [r.dimension_values[0].value for r in r3.rows]
    channel_data   = [int(r.metric_values[0].value) for r in r3.rows]

    # 5. Devices
    r4 = client.run_report(RunReportRequest(
        property=_prop(), date_ranges=dr,
        dimensions=[Dimension(name='deviceCategory')],
        metrics=[Metric(name='sessions')],
        order_bys=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name='sessions'), desc=True)],
    ), timeout=6)
    device_labels = [r.dimension_values[0].value.capitalize() for r in r4.rows]
    device_data   = [int(r.metric_values[0].value) for r in r4.rows]

    # 6. Referral sources
    r5 = client.run_report(RunReportRequest(
        property=_prop(), date_ranges=dr,
        dimensions=[Dimension(name='sessionSource')],
        metrics=[Metric(name='sessions')],
        order_bys=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name='sessions'), desc=True)],
        limit=6,
    ), timeout=6)
    referral_labels = [r.dimension_values[0].value for r in r5.rows]
    referral_data   = [int(r.metric_values[0].value) for r in r5.rows]

    # 7. Countries
    r6 = client.run_report(RunReportRequest(
        property=_prop(), date_ranges=dr,
        dimensions=[Dimension(name='country')],
        metrics=[Metric(name='activeUsers')],
        order_bys=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name='activeUsers'), desc=True)],
        limit=5,
    ), timeout=6)
    country_labels = [r.dimension_values[0].value for r in r6.rows]
    country_data   = [int(r.metric_values[0].value) for r in r6.rows]

    return {
        'ga_ok': True,
        'total_users':     total_users,
        'total_sessions':  total_sessions,
        'total_pageviews': total_pageviews,
        'bounce_rate':     bounce_rate,
        'avg_duration':    avg_duration,
        'ga_trend_labels': json.dumps(trend_labels),
        'trend_sessions':  json.dumps(trend_sessions),
        'trend_users':     json.dumps(trend_users),
        'top_pages':       top_pages,
        'channel_labels':  json.dumps(channel_labels),
        'channel_data':    json.dumps(channel_data),
        'device_labels':   json.dumps(device_labels),
        'device_data':     json.dumps(device_data),
        'referral_labels': json.dumps(referral_labels),
        'referral_data':   json.dumps(referral_data),
        'country_labels':  json.dumps(country_labels),
        'country_data':    json.dumps(country_data),
    }


def _friendly_error(exc):
    err = str(exc)
    if any(k in err for k in ('DNS', 'hostname', 'lookup', 'resolving', 'Could not contact')):
        return 'Google Analytics API unreachable — check network/firewall'
    if any(k in err.lower() for k in ('credentials', 'permission')) or '403' in err:
        return 'GA credentials error — check service account permissions'
    if 'quota' in err.lower() or '429' in err:
        return 'GA quota exceeded — data will refresh shortly'
    return f'GA unavailable: {err[:120]}'


def get_ga_dashboard_data(days=30):
    """
    Fetch all GA4 data needed for the CMS analytics page.
    Uses a thread with a hard 12-second timeout so DNS hangs never block the server.
    Returns safe empty defaults on any failure. Cached 30 minutes on success.
    """
    cache_key = GA_CACHE_KEY.format(days=days)
    cached = cache.get(cache_key)
    if cached:
        return cached

    from concurrent.futures import ThreadPoolExecutor, TimeoutError as _Timeout
    try:
        with ThreadPoolExecutor(max_workers=1) as ex:
            future = ex.submit(_do_fetch, days)
            result = future.result(timeout=12)
        cache.set(cache_key, result, GA_CACHE_TTL)
        return result
    except _Timeout:
        return {**_GA_EMPTY, 'ga_error': 'Google Analytics API unreachable — check network/firewall'}
    except Exception as exc:
        return {**_GA_EMPTY, 'ga_error': _friendly_error(exc)}
