import sys
import os
import asyncio
import traceback
from datetime import datetime
from http.client import responses as HTTP_STATUS_PHRASES

APP_ROOT = '/home/cognxzmz/skld-api.cogneura-apps.com'
DIAG_FILE = os.path.join(APP_ROOT, 'passenger_diag.txt')


def _log(msg):
    try:
        with open(DIAG_FILE, 'a') as f:
            f.write(f'[{datetime.now().isoformat()}] {msg}\n')
    except Exception:
        pass


def _run_lifespan_startup(asgi_app):
    """Send lifespan.startup to FastAPI so it runs its startup hooks."""
    startup_complete = False
    startup_failed = False
    failure_message = ''

    async def receive():
        nonlocal startup_complete
        if not startup_complete:
            startup_complete = True
            return {'type': 'lifespan.startup'}
        # After startup, keep lifespan open (block forever - we'll cancel)
        await asyncio.sleep(3600)
        return {'type': 'lifespan.shutdown'}

    async def send(message):
        nonlocal startup_failed, failure_message
        if message['type'] == 'lifespan.startup.complete':
            _log('lifespan.startup.complete received')
        elif message['type'] == 'lifespan.startup.failed':
            startup_failed = True
            failure_message = message.get('message', 'unknown')
            _log(f'lifespan.startup.failed: {failure_message}')

    async def run():
        try:
            task = asyncio.ensure_future(asgi_app({'type': 'lifespan', 'asgi': {'version': '3.0'}}, receive, send))
            # Wait a bit for startup to finish, then cancel the lifespan task
            for _ in range(50):  # up to 5 seconds
                await asyncio.sleep(0.1)
                if startup_complete or startup_failed:
                    break
            # Don't cancel - let the lifespan keep running? No, we can't keep a loop running.
            # Just cancel it; the app is initialized.
            task.cancel()
            try:
                await task
            except asyncio.CancelledError:
                pass
        except Exception as e:
            _log(f'lifespan error: {e}')

    loop = asyncio.new_event_loop()
    try:
        loop.run_until_complete(run())
    finally:
        loop.close()

    if startup_failed:
        raise RuntimeError(f'FastAPI lifespan startup failed: {failure_message}')


def asgi_to_wsgi(asgi_app):
    """Minimal ASGI-to-WSGI adapter for sync FastAPI apps on Passenger."""
    def wsgi_app(environ, start_response):
        try:
            method = environ['REQUEST_METHOD']
            path = environ.get('PATH_INFO', '/')
            _log(f'REQUEST: {method} {path}')

            scope = {
                'type': 'http',
                'asgi': {'version': '3.0'},
                'http_version': environ.get('SERVER_PROTOCOL', 'HTTP/1.1').split('/')[-1],
                'method': environ['REQUEST_METHOD'],
                'path': environ.get('PATH_INFO', '/'),
                'query_string': environ.get('QUERY_STRING', '').encode('latin-1'),
                'root_path': environ.get('SCRIPT_NAME', ''),
                'scheme': environ.get('wsgi.url_scheme', 'https'),
                'server': (environ['SERVER_NAME'], int(environ.get('SERVER_PORT', '443'))),
                'headers': [],
            }
            for key, val in environ.items():
                if key.startswith('HTTP_'):
                    name = key[5:].lower().replace('_', '-').encode('latin-1')
                    scope['headers'].append((name, val.encode('latin-1')))
                elif key == 'CONTENT_TYPE':
                    scope['headers'].append((b'content-type', val.encode('latin-1')))
                elif key == 'CONTENT_LENGTH' and val:
                    scope['headers'].append((b'content-length', val.encode('latin-1')))

            content_length = int(environ.get('CONTENT_LENGTH') or 0)
            if content_length > 0 and environ.get('wsgi.input'):
                body = environ['wsgi.input'].read(content_length)
            else:
                body = b''
            received = False

            status_code = 500
            response_headers = []
            body_parts = []

            async def receive():
                nonlocal received
                if not received:
                    received = True
                    return {'type': 'http.request', 'body': body, 'more_body': False}
                
                # FIX: Block forever instead of signaling a premature disconnect.
                # The event loop will close this automatically when the request finishes.
                await asyncio.Event().wait()

            async def send(message):
                nonlocal status_code, response_headers
                msg_type = message.get('type', '?')
                if msg_type == 'http.response.start':
                    status_code = message['status']
                    response_headers = message.get('headers', [])
                    _log(f'SEND: {msg_type} status={status_code}')
                elif msg_type == 'http.response.body':
                    raw = message.get('body')
                    chunk = raw or b''
                    _log(f'SEND: {msg_type} raw_type={type(raw).__name__} raw_len={len(raw) if raw is not None else "None"} chunk_len={len(chunk)}')
                    if chunk:
                        body_parts.append(chunk)
                else:
                    _log(f'SEND: {msg_type} (unhandled)')

            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            try:
                loop.run_until_complete(asgi_app(scope, receive, send))
            finally:
                loop.close()
                asyncio.set_event_loop(None)

            status_phrase = HTTP_STATUS_PHRASES.get(status_code, 'Unknown')
            status = f'{status_code} {status_phrase}'
            headers = [(n.decode('latin-1'), v.decode('latin-1')) for n, v in response_headers]
            total_bytes = sum(len(p) for p in body_parts)
            _log(f'RESPONSE: {status} body={total_bytes}b')
            start_response(status, headers)
            return body_parts

        except Exception as e:
            _log(f'REQUEST ERROR: {e}\n{traceback.format_exc()}')
            error_body = f'Internal Server Error: {e}'.encode('utf-8')
            start_response('500 Internal Server Error', [
                ('Content-Type', 'text/plain'),
                ('Content-Length', str(len(error_body)))
            ])
            return [error_body]

    return wsgi_app


try:
    _log('--- passenger_wsgi.py loading ---')
    _log(f'Python: {sys.version}')
    _log(f'CWD: {os.getcwd()}')

    sys.path.insert(0, APP_ROOT)

    os.environ.setdefault('SECRET_KEY', 'changeme-set-real-secret-in-htaccess')
    os.environ.setdefault('DATABASE_URL', f'sqlite:///{APP_ROOT}/quiz.db')
    os.environ.setdefault('ALLOWED_ORIGINS', '["https://skld.cogneura-apps.com"]')

    # Run Alembic migrations automatically on every deploy so the DB schema
    # is always in sync with the models - no manual migration step needed.
    try:
        from alembic.config import Config as AlembicConfig
        from alembic import command as alembic_command
        alembic_cfg = AlembicConfig(os.path.join(APP_ROOT, 'alembic.ini'))
        alembic_cfg.set_main_option('script_location', os.path.join(APP_ROOT, 'migrations'))
        alembic_cfg.set_main_option('sqlalchemy.url', os.environ.get('DATABASE_URL', f'sqlite:///{APP_ROOT}/quiz.db'))
        alembic_command.upgrade(alembic_cfg, 'head')
        _log('alembic upgrade head: done')
    except Exception as mig_err:
        _log(f'alembic upgrade WARNING: {mig_err}')
        # Fall back to create_all so the app can still start on a fresh DB
        from app.core.db import init_db
        init_db()
        _log('init_db() fallback done')
    else:
        # Alembic handles table creation; init_db still needed for WAL mode etc.
        from app.core.db import init_db
        init_db()
        _log('init_db() done')

    from app.main import app as fastapi_app
    _log('FastAPI app imported')

    # Run lifespan startup so FastAPI is fully initialized
    _run_lifespan_startup(fastapi_app)
    _log('lifespan startup done')

    application = asgi_to_wsgi(fastapi_app)
    _log('WSGI wrapper ready - application callable set')

except Exception as e:
    _log(f'FATAL: {e}\n{traceback.format_exc()}')
    # Provide a fallback WSGI app that returns the error
    def application(environ, start_response):
        msg = f'App failed to load: {e}'.encode('utf-8')
        start_response('500 Internal Server Error', [
            ('Content-Type', 'text/plain'),
            ('Content-Length', str(len(msg)))
        ])
        return [msg]