import tempfile
import time
import unittest.mock
from pathlib import Path

from django.contrib.auth import get_user_model
from django.core.cache import cache
from django.test import TestCase
from django.urls import reverse


class ServerStatsTests(TestCase):
    def setUp(self):
        User = get_user_model()
        self.staff = User.objects.create_user(
            username='Boss', password='pw', is_staff=True,
        )
        self.normal = User.objects.create_user(username='NormalUser', password='pw')
        cache.clear()

    def _authorized(self, user=None):
        session = self.client.session
        session['authorized'] = True
        session.save()
        if user is not None:
            self.client.force_login(user)
        return session

    def test_staff_api_returns_snapshot(self):
        self._authorized(self.staff)
        resp = self.client.get(reverse('landing:server_stats_api'))
        self.assertEqual(resp.status_code, 200)
        data = resp.json()
        for key in ('app', 'system', 'content', 'recent', 'traffic', 'generated_at'):
            self.assertIn(key, data)
        self.assertIn('users', data['content'])
        self.assertIn('versions', data['recent'])

    def test_api_payload_cached(self):
        self._authorized(self.staff)
        first = self.client.get(reverse('landing:server_stats_api')).json()
        second = self.client.get(reverse('landing:server_stats_api')).json()
        self.assertEqual(first['generated_at'], second['generated_at'])

    def test_non_staff_api_forbidden(self):
        self._authorized(self.normal)
        resp = self.client.get(reverse('landing:server_stats_api'))
        self.assertEqual(resp.status_code, 403)
        self.assertEqual(resp.json()['detail'], 'Only staff can view server stats.')

    def test_page_is_staff_shell(self):
        self._authorized(self.staff)
        resp = self.client.get(reverse('landing:server_stats'))
        self.assertEqual(resp.status_code, 200)
        self.assertContains(resp, 'stats-updated')
        self.assertContains(resp, '/api/stats/')

    def test_page_forbidden_for_non_staff(self):
        self._authorized(self.normal)
        resp = self.client.get(reverse('landing:server_stats'))
        self.assertEqual(resp.status_code, 403)

    def test_anonymous_redirects_to_login(self):
        self._authorized()
        resp = self.client.get(reverse('landing:server_stats'))
        self.assertEqual(resp.status_code, 302)


class TrafficStatsTests(TestCase):
    def test_parses_access_log(self):
        from landing import views
        with tempfile.TemporaryDirectory() as tmp:
            log = Path(tmp) / 'gunicorn-access.log'
            now = time.strftime('%d/%b/%Y:%H:%M:%S %z', time.localtime())
            log.write_text(''.join([
                f'127.0.0.1 [{now}] "GET /home/ HTTP/1.1" 200 1200 0.004\n',
                f'127.0.0.1 [{now}] "POST /api/uploads/ HTTP/1.1" 200 80 0.020\n',
                f'127.0.0.1 [{now}] "GET /packs/test-pack/ HTTP/1.1" 404 300 0.001\n',
                f'10.0.0.5 [{now}] "GET /api/stats/ HTTP/1.1" 500 50 0.150\n',
            ]))
            with unittest.mock.patch.object(views, 'GUNICORN_ACCESS_LOG', log):
                stats = views._traffic_stats()
        self.assertIsNotNone(stats)
        self.assertEqual(stats['requests_total'], 4)
        self.assertEqual(stats['requests_min'], 4)
        self.assertEqual(stats['status'], {'2xx': 2, '3xx': 0, '4xx': 1, '5xx': 1})
        self.assertEqual(stats['max_ms'], 150.0)
        self.assertEqual(len(stats['recent']), 4)
        self.assertEqual(stats['recent'][0]['status'], 500)

    def test_missing_log_returns_none(self):
        from landing import views
        with tempfile.TemporaryDirectory() as tmp:
            log = Path(tmp) / 'nope.log'
            with unittest.mock.patch.object(views, 'GUNICORN_ACCESS_LOG', log):
                self.assertIsNone(views._traffic_stats())
