﻿# v2026-07-03-edit-bag-sign-fix
import sys
import os
import logging
import asyncio
import json

_APP_DIR = os.path.dirname(os.path.abspath(__file__))
LOG_FILE = os.path.join(_APP_DIR, 'app.log')
_handlers = [logging.StreamHandler(sys.stderr)]
try:
    _handlers.insert(0, logging.FileHandler(LOG_FILE))
except Exception:
    pass

# Support local vendor packages (pip install --target=vendor)
_vendor = os.path.join(_APP_DIR, 'vendor')
if os.path.isdir(_vendor):
    sys.path.insert(0, _vendor)
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s %(levelname)s: %(message)s',
    handlers=_handlers,
)
logger = logging.getLogger('wsgi')
logger.info('wsgi.py loading...')


def _status_phrase(code):
    phrases = {
        200: 'OK', 201: 'Created', 204: 'No Content',
        301: 'Moved Permanently', 302: 'Found', 304: 'Not Modified',
        400: 'Bad Request', 401: 'Unauthorized', 403: 'Forbidden',
        404: 'Not Found', 405: 'Method Not Allowed', 409: 'Conflict',
        422: 'Unprocessable Entity', 500: 'Internal Server Error',
    }
    return phrases.get(code, 'Unknown')


async def _asgi_call(asgi_app, environ):
    """Run one HTTP request through an ASGI app; returns (status, headers, body)."""
    headers = []
    for key, val in environ.items():
        if key.startswith('HTTP_'):
            name = key[5:].lower().replace('_', '-').encode('latin-1')
            headers.append((name, val.encode('latin-1')))
    if 'CONTENT_TYPE' in environ and environ['CONTENT_TYPE']:
        headers.append((b'content-type', environ['CONTENT_TYPE'].encode('latin-1')))
    if 'CONTENT_LENGTH' in environ and environ['CONTENT_LENGTH']:
        headers.append((b'content-length', environ['CONTENT_LENGTH'].encode('latin-1')))

    scope = {
        'type': 'http',
        'asgi': {'version': '3.0'},
        'http_version': environ.get('SERVER_PROTOCOL', 'HTTP/1.1').rsplit('/', 1)[-1],
        'method': environ['REQUEST_METHOD'].upper(),
        'headers': headers,
        '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.get('SERVER_NAME', 'localhost'),
                   int(environ.get('SERVER_PORT', 443))),
        'client': None,
    }

    try:
        content_length = int(environ.get('CONTENT_LENGTH') or 0)
    except (ValueError, TypeError):
        content_length = 0
    body_bytes = environ['wsgi.input'].read(content_length) if content_length > 0 else b''
    received = False

    async def receive():
        nonlocal received
        if not received:
            received = True
            return {'type': 'http.request', 'body': body_bytes, 'more_body': False}
        return {'type': 'http.disconnect'}

    response_status = None
    response_headers = []
    response_body = []

    async def send(message):
        nonlocal response_status, response_headers
        if message['type'] == 'http.response.start':
            response_status = message['status']
            response_headers = message.get('headers', [])
        elif message['type'] == 'http.response.body':
            if message.get('body'):
                response_body.append(message['body'])

    await asgi_app(scope, receive, send)
    if response_status is None:
        raise RuntimeError('ASGI app did not send http.response.start')
    return response_status, response_headers, response_body


# â”€â”€ Step 1: Import the FastAPI app â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
fastapi_app = None
_import_error = None

try:
    sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
    logger.info('Importing app.main...')
    from app.main import app as fastapi_app
    logger.info('app.main imported successfully.')
except Exception as _exc:
    _import_error = str(_exc)
    logger.error('Failed to import app.main', exc_info=True)

# â”€â”€ Step 2: DB init â€” non-fatal, app still runs if DB is temporarily down â”€â”€â”€
if fastapi_app is not None:
    try:
        logger.info('Running DB init...')
        from app.database import engine, Base
        from app import models as _models  # register all models on Base.metadata
        Base.metadata.create_all(engine)
        logger.info('All tables OK.')

        from app.models import SystemSetting
        from app.database import SessionLocal
        defaults = [
            ('shift.EM.daily_allowance',  '500',  'Daily shift allowance for Early Morning shift (Rs.)'),
            ('shift.DAY.daily_allowance', '1000', 'Daily shift allowance for Day shift (Rs.)'),
            ('shift.MS.daily_allowance',  '1000', 'Daily shift allowance for Morning shift (Rs.)'),
            ('shift.MG.daily_allowance',  '800',  'Daily shift allowance for MG Morning shift (Rs.)'),
            ('shift.ES.daily_allowance',  '1000', 'Daily shift allowance for Evening shift (Rs.)'),
            ('tank.LP92.capacity',  '40000', 'LP92 tank capacity in litres'),
            ('tank.LP92.reorder',   '5000',  'LP92 reorder threshold in litres'),
            ('tank.EURO3.capacity', '40000', 'EURO3 tank capacity in litres'),
            ('tank.EURO3.reorder',  '5000',  'EURO3 reorder threshold in litres'),
            ('tank.LAD.capacity',   '40000', 'LAD tank capacity in litres'),
            ('tank.LAD.reorder',    '5000',  'LAD reorder threshold in litres'),
            ('tank.LADXM.capacity', '20000', 'LADXM tank capacity in litres'),
            ('tank.LADXM.reorder',  '3000',  'LADXM reorder threshold in litres'),
            ('notifications_cron_key', 'notif-cron-dbs2026', 'Pre-shared key for cron-triggered alert checks'),
        ]
        db = SessionLocal()
        try:
            for key, value, desc in defaults:
                if not db.query(SystemSetting).filter(SystemSetting.setting_key == key).first():
                    db.add(SystemSetting(setting_key=key, setting_value=value, description=desc))
            db.commit()
        finally:
            db.close()

        _sa_text = __import__('sqlalchemy').text
        _migrations = [
            ("allowances ENUM+DAILY",
             "ALTER TABLE allowances MODIFY COLUMN allowance_type "
             "ENUM('OVERTIME','PERFORMANCE','ATTENDANCE','DAILY') NOT NULL"),
            ("audit_log.summary",
             "ALTER TABLE audit_log ADD COLUMN summary VARCHAR(255) NULL AFTER record_id"),
            ("pumps.status",
             "ALTER TABLE pumps ADD COLUMN status ENUM('ACTIVE','MAINTENANCE','INACTIVE') NOT NULL DEFAULT 'ACTIVE'"),
            ("pumps.maintenance_reason",
             "ALTER TABLE pumps ADD COLUMN maintenance_reason VARCHAR(255) NULL"),
            ("pumps.sort_order",
             "ALTER TABLE pumps ADD COLUMN sort_order INT NOT NULL DEFAULT 0"),
            ("pumps.uq_pump_number_fuel",
             "ALTER TABLE pumps ADD CONSTRAINT uq_pump_number_fuel_type UNIQUE (pump_number, fuel_type)"),
            ("pump_shift_configs.uq_pump_shift",
             "ALTER TABLE pump_shift_configs ADD CONSTRAINT uq_pump_shift_config UNIQUE (pump_id, shift_template_id)"),
        ]
        for _label, _sql in _migrations:
            try:
                with engine.connect() as _conn:
                    _conn.execute(_sa_text(_sql))
                    _conn.commit()
                logger.info(f'Migration OK: {_label}')
            except Exception as _me:
                logger.info(f'Migration skipped ({_label}): {_me}')

        try:
            from app.dip_chart_data import seed_dip_charts
            seed_dip_charts(engine)
            logger.info('Dip chart data seeded.')
        except Exception as _seed_exc:
            logger.warning(f'Dip chart seed skipped: {_seed_exc}')

        logger.info('DB init complete.')
    except Exception as _db_exc:
        # DB failure is non-fatal â€” app still handles requests (DB errors surface per-request)
        logger.error(f'DB init failed (app still running): {_db_exc}', exc_info=True)


def _cors_headers(origin):
    if not origin:
        return []
    return [
        ('Access-Control-Allow-Origin',      origin),
        ('Access-Control-Allow-Credentials', 'true'),
        ('Access-Control-Allow-Methods',     'GET, POST, PUT, PATCH, DELETE, OPTIONS'),
        ('Access-Control-Allow-Headers',     'Content-Type, Authorization, X-Requested-With'),
    ]


# â”€â”€ Step 3: Define WSGI application â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
if fastapi_app is not None:
    # Happy path: FastAPI handles everything including CORS and OPTIONS
    def application(environ, start_response):
        method = environ.get('REQUEST_METHOD', '?')
        path   = environ.get('PATH_INFO', '?')
        logger.info(f'>> {method} {path}')
        try:
            status, headers, body = asyncio.run(_asgi_call(fastapi_app, environ))
            status_line  = f'{status} {_status_phrase(status)}'
            wsgi_headers = [(k.decode('latin-1'), v.decode('latin-1')) for k, v in headers]
            start_response(status_line, wsgi_headers)
            logger.info(f'<< {status} {path}')
            return body
        except Exception as exc:
            logger.error(f'Request error: {exc}', exc_info=True)
            origin = environ.get('HTTP_ORIGIN', '')
            start_response('500 Internal Server Error',
                           [('Content-Type', 'application/json')] + _cors_headers(origin))
            return [b'{"detail":"Internal server error"}']

else:
    # Import failed â€” return 200 for OPTIONS so preflight passes, 500 with error for the rest
    def application(environ, start_response):
        method = environ.get('REQUEST_METHOD', '')
        origin = environ.get('HTTP_ORIGIN', '')
        cors   = _cors_headers(origin)
        if method == 'OPTIONS':
            start_response('200 OK', [('Content-Type', 'text/plain')] + cors)
            return [b'']
        start_response('500 Internal Server Error',
                       [('Content-Type', 'application/json')] + cors)
        return [json.dumps({'detail': 'Backend failed to start', 'error': _import_error}).encode()]

logger.info('wsgi.py ready.')






