from __future__ import annotations

import jwt
from fastapi import FastAPI
from fastapi.testclient import TestClient
from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY

from amsdal_ml.mcp_server.mcp_oauth import init_oauth_routes
from amsdal_ml.mcp_server.mcp_oauth import oauth_router
from amsdal_ml.mcp_server.mcp_oauth.services.dependencies import get_current_user_sub
from amsdal_ml.mcp_server.mcp_oauth.services.types import JWT_ALGORITHM
from amsdal_ml.mcp_server.mcp_oauth.services.types import JwtClaims

HTTP_OK = 200
HTTP_BAD_REQUEST = 400
HTTP_REDIRECT_TEMP = 307


def _build_app(user_sub: str | None = None) -> FastAPI:
    app = FastAPI()
    init_oauth_routes()
    if user_sub is not None:
        app.dependency_overrides[get_current_user_sub] = lambda: user_sub
    app.include_router(oauth_router)
    return app


def _authorize_params(client_id: str, **overrides: str) -> dict[str, str]:
    params = {
        'response_type': 'code',
        'client_id': client_id,
        'state': 'state-1',
        'code_challenge': 'challenge',
        'code_challenge_method': 'S256',
        'scope': 'files:read',
        'resource': 'https://mcp.example.com',
    }
    params.update(overrides)
    return params


def test_authorize_requires_state(oauth_signing_key, build_client_id):
    client_id = build_client_id(oauth_signing_key)
    app = _build_app()
    response = TestClient(app).get(
        '/oauth/authorize',
        params={
            'response_type': 'code',
            'client_id': client_id,
            'code_challenge': 'challenge',
            'code_challenge_method': 'S256',
            'scope': 'files:read',
            'resource': 'https://mcp.example.com',
        },
    )

    assert response.status_code == HTTP_422_UNPROCESSABLE_ENTITY


def test_authorize_returns_code_with_valid_user(oauth_signing_key, build_client_id):
    client_id = build_client_id(oauth_signing_key)
    app = _build_app(user_sub='user-123')

    response = TestClient(app).get('/oauth/authorize', params=_authorize_params(client_id), follow_redirects=False)

    assert response.status_code == HTTP_OK
    body = response.json()
    assert body['state'] == 'state-1'

    payload = jwt.decode(body['code'], oauth_signing_key, algorithms=[JWT_ALGORITHM])
    assert payload[JwtClaims.CLIENT_ID.value] == client_id
    assert payload[JwtClaims.SUBJECT.value] == 'user-123'
    assert payload[JwtClaims.CODE_CHALLENGE.value] == 'challenge'
    assert payload[JwtClaims.CODE_CHALLENGE_METHOD.value] == 'S256'
    assert payload[JwtClaims.STATE.value] == 'state-1'
    assert payload[JwtClaims.RESOURCE.value] == 'https://mcp.example.com'


def test_authorize_rejects_invalid_response_type(oauth_signing_key, build_client_id):
    client_id = build_client_id(oauth_signing_key)
    app = _build_app()

    response = TestClient(app).get(
        '/oauth/authorize',
        params=_authorize_params(client_id, response_type='token'),
    )

    assert response.status_code == HTTP_422_UNPROCESSABLE_ENTITY


def test_authorize_rejects_redirect_uri_not_allowed(oauth_signing_key, build_client_id):
    client_id = build_client_id(oauth_signing_key, redirect_uris=['https://allowed.example/callback'])
    app = _build_app(user_sub='user-123')

    response = TestClient(app).get(
        '/oauth/authorize',
        params=_authorize_params(client_id, redirect_uri='https://denied.example/callback'),
    )

    assert response.status_code == HTTP_BAD_REQUEST
    assert response.json()['detail'] == 'redirect_uri not allowed'


def test_authorize_requires_code_challenge(oauth_signing_key, build_client_id):
    client_id = build_client_id(oauth_signing_key)
    app = _build_app()
    params = _authorize_params(client_id)
    params.pop('code_challenge')

    response = TestClient(app).get('/oauth/authorize', params=params)

    assert response.status_code == HTTP_422_UNPROCESSABLE_ENTITY


def test_authorize_rejects_unsupported_code_challenge_method(oauth_signing_key, build_client_id):
    client_id = build_client_id(oauth_signing_key)
    app = _build_app()

    response = TestClient(app).get(
        '/oauth/authorize',
        params=_authorize_params(client_id, code_challenge_method='plain'),
    )

    assert response.status_code == HTTP_422_UNPROCESSABLE_ENTITY


def test_authorize_rejects_invalid_client_id(oauth_signing_key):  # noqa: ARG001
    app = _build_app(user_sub='user-123')

    response = TestClient(app).get(
        '/oauth/authorize',
        params=_authorize_params('not-a-jwt'),
    )

    assert response.status_code == HTTP_BAD_REQUEST
    assert response.json()['detail'] == 'invalid client_id'


def test_authorize_rejects_invalid_client_issuer(oauth_signing_key, build_client_id):
    client_id = build_client_id(oauth_signing_key, issuer='https://wrong-issuer.example')
    app = _build_app(user_sub='user-123')

    response = TestClient(app).get('/oauth/authorize', params=_authorize_params(client_id))

    assert response.status_code == HTTP_BAD_REQUEST
    assert response.json()['detail'] == 'invalid client issuer'


def test_authorize_redirects_to_login_when_no_user_context(oauth_signing_key, build_client_id):
    client_id = build_client_id(oauth_signing_key)
    app = _build_app()

    response = TestClient(app).get('/oauth/authorize', params=_authorize_params(client_id), follow_redirects=False)

    assert response.status_code == HTTP_REDIRECT_TEMP
    assert '/auth/login?next=' in response.headers['location']


def test_authorize_redirects_back_to_redirect_uri_with_code(oauth_signing_key, build_client_id):
    redirect_uri = 'https://chatgpt.com/connector_platform_oauth_redirect?x=1'
    client_id = build_client_id(oauth_signing_key, redirect_uris=[redirect_uri])
    app = _build_app(user_sub='user-123')

    response = TestClient(app).get(
        '/oauth/authorize',
        params=_authorize_params(
            client_id,
            redirect_uri=redirect_uri,
            state='state-xyz',
        ),
        follow_redirects=False,
    )

    assert response.status_code == HTTP_REDIRECT_TEMP
    location = response.headers['location']
    assert location.startswith('https://chatgpt.com/connector_platform_oauth_redirect?')
    assert 'x=1' in location
    assert 'code=' in location
    assert 'state=state-xyz' in location
