import json
import secrets
import requests
from datetime import datetime, timedelta
from functools import wraps
from collections import defaultdict

from django.conf import settings
from django.http import JsonResponse
from django.shortcuts import render, redirect
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie

from .models import User, Transaction, Budget, Category

def login_required(f):
    @wraps(f)
    def wrapper(request, *args, **kwargs):
        if not request.session.get('user_id'):
            return redirect('/finanse/login/')
        return f(request, *args, **kwargs)
    return wrapper

def get_current_user(request):
    uid = request.session.get('user_id')
    if not uid:
        return None
    try:
        return User.objects.get(pk=uid)
    except User.DoesNotExist:
        return None

@ensure_csrf_cookie
def index(request):
    if not request.session.get('user_id'):
        return redirect('/finanse/login/')
    user = get_current_user(request)
    return render(request, 'app/index.html', {'user': user})

@ensure_csrf_cookie
def login_view(request):
    if request.session.get('user_id'):
        return redirect('/finanse/')
    state = secrets.token_hex(16)
    request.session['oauth_state'] = state
    google_url = None
    if settings.GOOGLE_CLIENT_ID and settings.GOOGLE_CLIENT_SECRET:
        params = {
            'client_id':     settings.GOOGLE_CLIENT_ID,
            'redirect_uri':  settings.APP_URL + '/callback/?provider=google',
            'response_type': 'code',
            'scope':         'openid email profile',
            'state':         state,
            'prompt':        'select_account',
        }
        google_url = 'https://accounts.google.com/o/oauth2/v2/auth?' + \
                     requests.compat.urlencode(params)
    return render(request, 'app/login.html', {
        'google_url': google_url,
        'error': request.GET.get('error'),
    })

def callback(request):
    provider = request.GET.get('provider', '')
    code     = request.GET.get('code', '')
    state    = request.GET.get('state', '')
    saved_state = request.session.pop('oauth_state', '')
    if not secrets.compare_digest(saved_state, state):
        return redirect('/finanse/login/?error=oauth')
    email = name = None
    try:
        if provider == 'google':
            token_r = requests.post('https://oauth2.googleapis.com/token', data={
                'code':          code,
                'client_id':     settings.GOOGLE_CLIENT_ID,
                'client_secret': settings.GOOGLE_CLIENT_SECRET,
                'redirect_uri':  settings.APP_URL + '/callback/?provider=google',
                'grant_type':    'authorization_code',
            }, timeout=10)
            access_token = token_r.json().get('access_token')
            if not access_token:
                raise ValueError('Brak access_token')
            info = requests.get(
                'https://www.googleapis.com/oauth2/v3/userinfo',
                headers={'Authorization': f'Bearer {access_token}'},
                timeout=10
            ).json()
            email = info.get('email')
            name  = info.get('name', '')
        else:
            return redirect('/finanse/login/?error=oauth')
    except Exception:
        return redirect('/finanse/login/?error=oauth')
    if not email:
        return redirect('/finanse/login/?error=oauth')
    if email.lower() not in [e.lower() for e in settings.ALLOWED_EMAILS]:
        return redirect('/finanse/login/?error=unauthorized')
    user, _ = User.objects.get_or_create(
        email=email,
        defaults={'name': name or email.split('@')[0], 'provider': provider}
    )
    if user.name != name and name:
        user.name = name
        user.save()
    if not Category.objects.filter(user=user).exists():
        defaults = [
            ('Jedzenie', '🍔', '#D85A30'),
            ('Transport', '🚗', '#1D9E75'),
            ('Dom', '🏠', '#7F77DD'),
            ('Rozrywka', '🎮', '#378ADD'),
            ('Zakupy', '🛍️', '#D4537E'),
            ('Zdrowie', '💊', '#639922'),
            ('Kawiarnie', '☕', '#BA7517'),
            ('Inne', '📦', '#888780'),
        ]
        for name_cat, emoji, color in defaults:
            Category.objects.create(user=user, name=name_cat, emoji=emoji, color=color)
    request.session['user_id'] = user.pk
    request.session.cycle_key()
    return redirect('/finanse/')

def logout_view(request):
    request.session.flush()
    return render(request, 'app/logout.html')

# ── API ────────────────────────────────────────────────

@login_required
def api_data(request):
    user = get_current_user(request)
    txns = list(user.transactions.values('id', 'amount', 'category', 'note', 'txn_date'))
    for t in txns:
        t['amount'] = float(t['amount'])
        t['txn_date'] = t['txn_date'].isoformat() if hasattr(t['txn_date'], 'isoformat') else str(t['txn_date'])
    buds = {b.category: float(b.amount) for b in user.budgets.all()}
    cats = list(user.categories.values('id', 'name', 'emoji', 'color'))
    return JsonResponse({'ok': True, 'transactions': txns, 'budgets': buds, 'categories': cats})

@csrf_exempt
@login_required
def api_add(request):
    if request.method != 'POST':
        return JsonResponse({'ok': False}, status=405)
    try:
        body = json.loads(request.body)
    except Exception:
        return JsonResponse({'ok': False, 'error': 'Nieprawidłowe dane'}, status=422)
    amount   = round(float(body.get('amount', 0)), 2)
    category = str(body.get('category', '')).strip()
    note     = str(body.get('note', '')).strip()[:255]
    date_str = body.get('date', datetime.now().isoformat())
    if amount <= 0 or not category:
        return JsonResponse({'ok': False, 'error': 'Nieprawidłowe dane'}, status=422)
    try:
        txn_date = datetime.fromisoformat(date_str.replace('Z', '+00:00'))
    except ValueError:
        txn_date = datetime.now()
    user = get_current_user(request)
    txn = Transaction.objects.create(
        user=user, amount=amount, category=category, note=note, txn_date=txn_date
    )
    return JsonResponse({'ok': True, 'id': txn.pk})

@csrf_exempt
@login_required
def api_delete(request, txn_id):
    if request.method != 'DELETE':
        return JsonResponse({'ok': False}, status=405)
    user = get_current_user(request)
    Transaction.objects.filter(pk=txn_id, user=user).delete()
    return JsonResponse({'ok': True})

@csrf_exempt
@login_required
def api_budgets(request):
    if request.method != 'POST':
        return JsonResponse({'ok': False}, status=405)
    try:
        body = json.loads(request.body)
    except Exception:
        return JsonResponse({'ok': False}, status=422)
    user = get_current_user(request)
    for category, amount in body.items():
        Budget.objects.update_or_create(
            user=user, category=str(category).strip(),
            defaults={'amount': round(float(amount), 2)}
        )
    return JsonResponse({'ok': True})

@csrf_exempt
@login_required
def api_add_category(request):
    if request.method != 'POST':
        return JsonResponse({'ok': False}, status=405)
    try:
        body = json.loads(request.body)
    except Exception:
        return JsonResponse({'ok': False}, status=422)
    name  = str(body.get('name', '')).strip()
    emoji = str(body.get('emoji', '💰')).strip()
    color = str(body.get('color', '#BA7517')).strip()
    if not name:
        return JsonResponse({'ok': False, 'error': 'Nazwa jest wymagana'}, status=422)
    user = get_current_user(request)
    cat, created = Category.objects.get_or_create(
        user=user, name=name,
        defaults={'emoji': emoji, 'color': color}
    )
    if not created:
        return JsonResponse({'ok': False, 'error': 'Kategoria już istnieje'}, status=422)
    return JsonResponse({'ok': True, 'id': cat.pk, 'name': cat.name, 'emoji': cat.emoji, 'color': cat.color})

@csrf_exempt
@login_required
def api_delete_category(request, cat_id):
    if request.method != 'DELETE':
        return JsonResponse({'ok': False}, status=405)
    user = get_current_user(request)
    Category.objects.filter(pk=cat_id, user=user).delete()
    return JsonResponse({'ok': True})

@login_required
def api_stats(request):
    user = get_current_user(request)
    txns = list(user.transactions.values('amount', 'category', 'txn_date'))
    now = datetime.now()
    by_cat  = defaultdict(float)
    daily   = defaultdict(float)
    monthly = defaultdict(float)
    for t in txns:
        dt = t['txn_date']
        if not hasattr(dt, 'year'):
            dt = datetime.fromisoformat(str(dt))
        amt = float(t['amount'])
        if dt.year == now.year and dt.month == now.month:
            by_cat[t['category']] += amt
            daily[dt.strftime('%Y-%m-%d')] += amt
        if dt >= datetime(now.year, now.month, 1) - timedelta(days=180):
            monthly[dt.strftime('%Y-%m')] += amt
    daily_30 = {}
    for i in range(29, -1, -1):
        d = (now - timedelta(days=i)).strftime('%Y-%m-%d')
        daily_30[d] = round(daily.get(d, 0), 2)
    monthly_6 = {}
    for i in range(5, -1, -1):
        month = now.month - i
        year  = now.year
        while month <= 0:
            month += 12
            year  -= 1
        key = f'{year}-{month:02d}'
        monthly_6[key] = round(monthly.get(key, 0), 2)
    buds = {b.category: float(b.amount) for b in user.budgets.all()}
    return JsonResponse({
        'ok': True,
        'by_category': {k: round(v, 2) for k, v in by_cat.items()},
        'daily_trend': daily_30,
        'monthly':     monthly_6,
        'budgets':     buds,
    })
