# -*- coding: utf-8 -*-
"""End-to-end tests for admin authentication flow.

These tests simulate real user workflows through the admin authentication system.
Run with: pytest tests/e2e/test_admin_auth_e2e.py -v --run-integration
"""

import json
import pytest
from unittest.mock import patch

import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src'))

import mes_dashboard.core.database as db
from mes_dashboard.app import create_app
from mes_dashboard.services import page_registry
from mes_dashboard.routes import auth_routes


@pytest.fixture
def temp_page_status(tmp_path):
    """Create temporary page status file."""
    data_file = tmp_path / "page_status.json"
    initial_data = {
        "pages": [
            {"route": "/", "name": "首頁", "status": "released"},
            {"route": "/wip-overview", "name": "WIP 即時概況", "status": "released"},
            {"route": "/wip-detail", "name": "WIP 明細", "status": "released"},
            {"route": "/tables", "name": "表格總覽", "status": "dev"},
            {"route": "/resource", "name": "機台狀態", "status": "dev"},
        ],
        "api_public": True
    }
    data_file.write_text(json.dumps(initial_data, ensure_ascii=False), encoding="utf-8")
    return data_file


@pytest.fixture
def app(temp_page_status):
    """Create application for testing."""
    db._ENGINE = None

    # Mock page registry
    original_data_file = page_registry.DATA_FILE
    original_cache = page_registry._cache
    page_registry.DATA_FILE = temp_page_status
    page_registry._cache = None

    app = create_app('testing')
    app.config['TESTING'] = True
    app.config['CSRF_ENABLED'] = False

    yield app

    page_registry.DATA_FILE = original_data_file
    page_registry._cache = original_cache


@pytest.fixture
def client(app):
    """Create test client."""
    return app.test_client()


@pytest.fixture(autouse=True)
def clear_login_rate_limit():
    """Reset in-memory login attempts to avoid cross-test interference."""
    auth_routes._login_attempts.clear()
    yield
    auth_routes._login_attempts.clear()


def _mock_admin_user(mail: str = "ymirliu@panjit.com.tw") -> dict:
    return {
        "username": "92367",
        "displayName": "Test Admin",
        "mail": mail,
        "department": "Test Department",
    }


class TestFullLoginLogoutFlow:
    """E2E tests for complete login/logout flow."""

    @patch('mes_dashboard.routes.auth_routes.is_admin', return_value=True)
    @patch('mes_dashboard.routes.auth_routes.authenticate')
    def test_complete_admin_login_workflow(self, mock_auth, _mock_is_admin, client):
        """Test complete admin login workflow."""
        mock_auth.return_value = _mock_admin_user()
        spa_enabled = bool(client.application.config.get("PORTAL_SPA_ENABLED", False))

        # 1. Access portal entry.
        response = client.get("/", follow_redirects=False)
        if spa_enabled:
            assert response.status_code == 302
            assert response.location.rstrip("/").endswith("/portal-shell")
        else:
            assert response.status_code == 200
            content = response.data.decode("utf-8")
            assert "管理員登入" in content

        # 2. Go to login page
        response = client.get("/admin/login")
        assert response.status_code == 200

        # 3. Submit login form
        response = client.post("/admin/login", data={
            "username": "92367",
            "password": "password123"
        }, follow_redirects=True)

        assert response.status_code == 200
        if not spa_enabled:
            content = response.data.decode("utf-8")
            # Portal template should show admin identity in non-SPA mode.
            assert "Test Admin" in content or "登出" in content

        # 4. Verify session has admin
        with client.session_transaction() as sess:
            assert "admin" in sess
            assert sess["admin"]["mail"] == "ymirliu@panjit.com.tw"

        if spa_enabled:
            nav = client.get("/api/portal/navigation")
            assert nav.status_code == 200
            assert nav.get_json()["is_admin"] is True

        # 5. Access admin pages
        response = client.get("/admin/pages")
        assert response.status_code == 200

        # 6. Logout
        response = client.get("/admin/logout", follow_redirects=True)
        assert response.status_code == 200

        # 7. Verify logged out
        with client.session_transaction() as sess:
            assert "admin" not in sess

        # 8. Admin pages should redirect now
        response = client.get("/admin/pages", follow_redirects=False)
        assert response.status_code == 302


class TestPageAccessControlFlow:
    """E2E tests for page access control flow."""

    def test_non_admin_cannot_access_dev_pages(self, client, temp_page_status):
        """Test non-admin users cannot access dev pages."""
        # 1. Access released page - should work
        response = client.get("/wip-overview")
        assert response.status_code != 403

        # 2. Access dev page - should get 403
        response = client.get("/tables")
        assert response.status_code == 403
        content = response.data.decode("utf-8")
        assert "開發中" in content or "403" in content

    @patch('mes_dashboard.routes.auth_routes.is_admin', return_value=True)
    @patch('mes_dashboard.routes.auth_routes.authenticate')
    def test_admin_can_access_all_pages(self, mock_auth, _mock_is_admin, client, temp_page_status):
        """Test admin users can access all pages."""
        mock_auth.return_value = _mock_admin_user()

        # 1. Login as admin
        client.post("/admin/login", data={
            "username": "92367",
            "password": "password123"
        })

        # 2. Access released page - should work
        response = client.get("/wip-overview")
        assert response.status_code != 403

        # 3. Access dev page - should work for admin
        response = client.get("/tables")
        assert response.status_code != 403


class TestPageManagementFlow:
    """E2E tests for page management flow."""

    @patch('mes_dashboard.routes.auth_routes.is_admin', return_value=True)
    @patch('mes_dashboard.routes.auth_routes.authenticate')
    def test_admin_can_change_page_status(self, mock_auth, _mock_is_admin, client, temp_page_status):
        """Test admin can change page status via management interface."""
        mock_auth.return_value = _mock_admin_user()

        # 1. Login as admin
        client.post("/admin/login", data={
            "username": "92367",
            "password": "password123"
        })

        # 2. Get current pages list
        response = client.get("/admin/api/pages")
        assert response.status_code == 200
        data = json.loads(response.data)
        assert data["success"] is True

        # 3. Change /wip-overview from released to dev
        response = client.put(
            "/admin/api/pages/wip-overview",
            data=json.dumps({"status": "dev"}),
            content_type="application/json"
        )
        assert response.status_code == 200

        # 4. Verify change persisted
        page_registry._cache = None
        status = page_registry.get_page_status("/wip-overview")
        assert status == "dev"

        # 5. Logout
        client.get("/admin/logout")

        # 6. Now non-admin should get 403 on /wip-overview
        response = client.get("/wip-overview")
        assert response.status_code == 403

    @patch('mes_dashboard.routes.auth_routes.is_admin', return_value=True)
    @patch('mes_dashboard.routes.auth_routes.authenticate')
    def test_release_dev_page_makes_it_public(self, mock_auth, _mock_is_admin, client, temp_page_status):
        """Test releasing a dev page makes it publicly accessible."""
        mock_auth.return_value = _mock_admin_user()

        # 1. Verify /tables is currently dev (403 for non-admin)
        response = client.get("/tables")
        assert response.status_code == 403

        # 2. Login as admin
        client.post("/admin/login", data={
            "username": "92367",
            "password": "password123"
        })

        # 3. Release the page
        response = client.put(
            "/admin/api/pages/tables",
            data=json.dumps({"status": "released"}),
            content_type="application/json"
        )
        assert response.status_code == 200

        # 4. Logout
        client.get("/admin/logout")

        # 5. Clear cache and verify non-admin can access
        page_registry._cache = None
        response = client.get("/tables")
        assert response.status_code != 403


class TestPortalDynamicTabs:
    """E2E tests for dynamic portal tabs based on page status."""

    def test_portal_hides_dev_tabs_for_non_admin(self, client, temp_page_status):
        """Test portal hides dev page tabs for non-admin users."""
        response = client.get("/api/portal/navigation")
        assert response.status_code == 200
        payload = response.get_json()
        routes = {
            page["route"]
            for drawer in payload.get("drawers", [])
            for page in drawer.get("pages", [])
        }
        assert "/wip-overview" in routes
        assert "/tables" not in routes
        assert payload.get("is_admin") is False

    @patch('mes_dashboard.routes.auth_routes.is_admin', return_value=True)
    @patch('mes_dashboard.routes.auth_routes.authenticate')
    def test_portal_shows_all_tabs_for_admin(self, mock_auth, _mock_is_admin, client, temp_page_status):
        """Test portal shows all tabs for admin users."""
        mock_auth.return_value = _mock_admin_user()

        # Login as admin
        client.post("/admin/login", data={
            "username": "92367",
            "password": "password123"
        })

        response = client.get("/api/portal/navigation")
        assert response.status_code == 200
        payload = response.get_json()
        routes = {
            page["route"]
            for drawer in payload.get("drawers", [])
            for page in drawer.get("pages", [])
        }
        assert "/wip-overview" in routes
        assert "/tables" in routes
        assert payload.get("is_admin") is True


class TestSessionPersistence:
    """E2E tests for session persistence."""

    @patch('mes_dashboard.routes.auth_routes.is_admin', return_value=True)
    @patch('mes_dashboard.routes.auth_routes.authenticate')
    def test_session_persists_across_requests(self, mock_auth, _mock_is_admin, client):
        """Test admin session persists across multiple requests."""
        mock_auth.return_value = _mock_admin_user()

        # Login
        client.post("/admin/login", data={
            "username": "92367",
            "password": "password123"
        })

        # Make multiple requests
        for _ in range(5):
            response = client.get("/admin/pages")
            assert response.status_code == 200

        # Session should still be valid
        with client.session_transaction() as sess:
            assert "admin" in sess


class TestSecurityScenarios:
    """E2E tests for security scenarios."""

    def test_cannot_access_admin_api_without_login(self, client):
        """Test admin APIs are protected."""
        # Try to get pages without login
        response = client.get("/admin/api/pages", follow_redirects=False)
        assert response.status_code in (302, 401)

        # Try to update page without login
        response = client.put(
            "/admin/api/pages/wip-overview",
            data=json.dumps({"status": "dev"}),
            content_type="application/json",
            follow_redirects=False
        )
        assert response.status_code in (302, 401)

    @patch('mes_dashboard.routes.auth_routes.is_admin', return_value=False)
    @patch('mes_dashboard.routes.auth_routes.authenticate')
    def test_non_admin_user_cannot_login(self, mock_auth, _mock_is_admin, client):
        """Test non-admin user cannot access admin features."""
        mock_auth.return_value = {
            "username": "99999",
            "displayName": "Regular User",
            "mail": "regular@panjit.com.tw",
            "department": "Test",
        }

        # Try to login
        response = client.post("/admin/login", data={
            "username": "99999",
            "password": "password123"
        })

        # Should fail (show error, not redirect)
        assert response.status_code == 200
        content = response.data.decode("utf-8")
        assert "管理員" in content or "error" in content.lower()

        # Should NOT have admin session
        with client.session_transaction() as sess:
            assert "admin" not in sess


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
