"""Web panel for ADS1115 with I2C detection - piADC package."""

import os
import subprocess
import json
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any
import asyncio
import time

from ..service import get_service, ADS1115Config, reset_service


def create_web_app() -> FastAPI:
    """Create and configure FastAPI web panel application."""
    app = FastAPI(
        title="piADC - ADS1115 Web Panel",
        description="Web panel for ADS1115 ADC with I2C detection",
        version="1.1.0"
    )

    # WebSocket connections for live data
    class ConnectionManager:
        def __init__(self):
            self.active_connections: List[WebSocket] = []

        async def connect(self, websocket: WebSocket):
            await websocket.accept()
            self.active_connections.append(websocket)

        def disconnect(self, websocket: WebSocket):
            if websocket in self.active_connections:
                self.active_connections.remove(websocket)

        async def broadcast(self, message: dict):
            disconnected = []
            for connection in self.active_connections:
                try:
                    await connection.send_json(message)
                except:
                    disconnected.append(connection)
            
            for conn in disconnected:
                self.disconnect(conn)

    manager = ConnectionManager()

    class ChannelRead(BaseModel):
        channel: int = Field(..., ge=0, le=3, description="ADC channel (0-3)")
        raw_value: int
        voltage: float
        gain: float
        data_rate: int

    class AllChannelsRead(BaseModel):
        channels: List[ChannelRead]
        mock_mode: bool
        i2c_address: str
        timestamp: float

    class DeviceConfig(BaseModel):
        i2c_address: str = "0x48"
        gain: float = 1.0
        data_rate: int = 128
        mode: str = "single"
        mock_mode: bool = False
        available_gains: List[float] = []
        available_data_rates: List[int] = []

    class ConfigUpdate(BaseModel):
        gain: Optional[float] = Field(None, ge=2/3, le=16)
        data_rate: Optional[int] = Field(None, ge=8, le=860)
        i2c_address: Optional[str] = None
        mode: Optional[str] = Field(None, pattern="^(single|continuous)$")

    class I2CDevice(BaseModel):
        address: str
        description: str
        detected: bool = True

    class I2CScanResult(BaseModel):
        bus: int
        devices: List[I2CDevice]
        ads1115_detected: bool
        ads1115_address: Optional[str] = None

    class ChannelRequest(BaseModel):
        channel: int = Field(..., ge=0, le=3, description="ADC channel to read (0-3)")

    class LiveDataSettings(BaseModel):
        enabled: bool
        interval: float = 1.0  # seconds

    def scan_i2c_bus(bus: int = 1) -> I2CScanResult:
        """Scan I2C bus for connected devices."""
        devices = []
        ads1115_detected = False
        ads1115_address = None
        
        # Known I2C device descriptions
        known_devices = {
            0x48: "ADS1115/ADS1015 (ADDR=GND)",
            0x49: "ADS1115/ADS1015 (ADDR=VDD)",
            0x4A: "ADS1115/ADS1015 (ADDR=SDA)",
            0x4B: "ADS1115/ADS1015 (ADDR=SCL)",
            0x20: "MCP23017 (ADDR=A0+A1+A2=GND)",
            0x21: "MCP23017",
            0x22: "MCP23017",
            0x23: "MCP23017",
            0x27: "MCP23017 (ADDR=A0+A1+A2=VDD)",
            0x40: "PCA9685",
            0x41: "PCA9685",
            0x50: "EEPROM",
            0x68: "DS1307/MPU6050/PCF8523",
            0x76: "BME280/BMP280 (ADDR=SDO=GND)",
            0x77: "BME280/BMP280 (ADDR=SDO=VDD)",
        }
        
        try:
            # Try using i2cdetect if available
            result = subprocess.run(
                ["i2cdetect", "-y", str(bus)],
                capture_output=True,
                text=True,
                timeout=5
            )
            
            if result.returncode == 0:
                # Parse i2cdetect output
                for line in result.stdout.split('\n')[1:]:
                    parts = line.split()[1:]  # Skip row number
                    for i, addr_str in enumerate(parts):
                        if addr_str not in ['--', ''] and not addr_str.startswith('UU'):
                            try:
                                if addr_str.startswith('0x'):
                                    addr = int(addr_str, 16)
                                else:
                                    # Calculate address from row/column
                                    row = int(line.split()[0].rstrip(':'), 16)
                                    addr = row + i
                                
                                desc = known_devices.get(addr, "Unknown device")
                                addr_hex = f"0x{addr:02X}"
                                devices.append(I2CDevice(
                                    address=addr_hex,
                                    description=desc,
                                    detected=True
                                ))
                                
                                if addr in [0x48, 0x49, 0x4A, 0x4B]:
                                    ads1115_detected = True
                                    ads1115_address = addr_hex
                            except (ValueError, IndexError):
                                continue
            else:
                # Fallback: try to detect ADS1115 using Python
                try:
                    import board
                    import busio
                    from adafruit_ads1x15.ads1115 import ADS1115
                    
                    i2c = busio.I2C(board.SCL, board.SDA)
                    for addr in [0x48, 0x49, 0x4A, 0x4B]:
                        try:
                            adc = ADS1115(i2c, address=addr)
                            # If we can create instance, device exists
                            addr_hex = f"0x{addr:02X}"
                            desc = known_devices.get(addr, "ADS1115")
                            devices.append(I2CDevice(
                                address=addr_hex,
                                description=desc,
                                detected=True
                            ))
                            ads1115_detected = True
                            ads1115_address = addr_hex
                        except:
                            pass
                    i2c.unlock()
                except ImportError:
                    pass
        except (subprocess.SubprocessError, FileNotFoundError):
            # i2cdetect not available, use mock data for testing
            if os.getenv('ADS1115_MOCK', 'false').lower() == 'true':
                devices.append(I2CDevice(
                    address="0x48",
                    description="ADS1115 (MOCK MODE)",
                    detected=True
                ))
                ads1115_detected = True
                ads1115_address = "0x48"
        
        return I2CScanResult(
            bus=bus,
            devices=devices,
            ads1115_detected=ads1115_detected,
            ads1115_address=ads1115_address
        )

    @app.get("/", response_class=HTMLResponse)
    async def web_panel():
        """Serve web panel HTML."""
        return HTMLResponse(content=get_html_content())

    @app.get("/api/health")
    async def health():
        """Check ADC service health."""
        service = get_service()
        return {
            "status": "healthy",
            "initialized": service._adc is not None,
            "mock_mode": service.is_mock,
            "timestamp": time.time()
        }

    @app.get("/api/i2c/scan", response_model=I2CScanResult)
    async def scan_i2c(bus: int = 1):
        """Scan I2C bus for devices."""
        return scan_i2c_bus(bus)

    @app.get("/api/config", response_model=DeviceConfig)
    async def get_config():
        """Get current device configuration."""
        service = get_service()
        config = service.get_config()
        return DeviceConfig(**config)

    @app.post("/api/config", response_model=DeviceConfig)
    async def update_config(update: ConfigUpdate):
        """Update device configuration."""
        service = get_service()
        kwargs = {k: v for k, v in update.model_dump().items() if v is not None}
        config = service.update_config(**kwargs)
        return DeviceConfig(**config)

    @app.get("/api/read/all", response_model=AllChannelsRead)
    async def read_all():
        """Read all 4 ADC channels."""
        service = get_service()
        result = service.read_all_channels()
        result['timestamp'] = time.time()
        return AllChannelsRead(**result)

    @app.get("/api/read/{channel}", response_model=ChannelRead)
    async def read_channel(channel: int):
        """Read a single ADC channel."""
        if channel < 0 or channel > 3:
            raise HTTPException(status_code=400, detail="Channel must be 0-3")
        
        service = get_service()
        result = service.read_channel(channel)
        return ChannelRead(**result)

    @app.post("/api/read/channel", response_model=ChannelRead)
    async def read_channel_post(request: ChannelRequest):
        """Read a single ADC channel (POST method)."""
        service = get_service()
        result = service.read_channel(request.channel)
        return ChannelRead(**result)

    @app.post("/api/reset")
    async def reset_service_endpoint():
        """Reset ADC service."""
        reset_service()
        return {"status": "reset", "message": "Service reset. Call /api/health to reinitialize."}

    @app.websocket("/ws")
    async def websocket_endpoint(websocket: WebSocket):
        """WebSocket for live ADC data streaming."""
        await manager.connect(websocket)
        try:
            while True:
                # Wait for messages from client
                data = await websocket.receive_json()
                
                if data.get('action') == 'start_live':
                    interval = data.get('interval', 1.0)
                    while True:
                        try:
                            service = get_service()
                            result = service.read_all_channels()
                            result['timestamp'] = time.time()
                            result['type'] = 'live_data'
                            await websocket.send_json(result)
                        except Exception as e:
                            await websocket.send_json({
                                'type': 'error',
                                'message': str(e)
                            })
                        
                        # Check for stop message
                        try:
                            msg = await asyncio.wait_for(
                                websocket.receive_json(),
                                timeout=interval
                            )
                            if msg.get('action') == 'stop_live':
                                break
                        except asyncio.TimeoutError:
                            pass
        except WebSocketDisconnect:
            manager.disconnect(websocket)

    return app


# Global app instance for uvicorn
app = create_web_app()

def get_html_content() -> str:
    """Get web panel HTML content."""
    return '''<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>piADC - ADS1115 Web Panel</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; }
        .container { max-width: 1200px; margin: 0 auto; }
        h1 { color: white; text-align: center; margin-bottom: 30px; font-size: 2.5rem; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); }
        .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 20px; }
        .card { background: white; border-radius: 16px; padding: 24px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); }
        .card h2 { color: #333; margin-bottom: 20px; font-size: 1.3rem; border-bottom: 2px solid #667eea; padding-bottom: 10px; }
        .status { display: flex; align-items: center; gap: 10px; margin: 10px 0; }
        .status-dot { width: 12px; height: 12px; border-radius: 50%; }
        .status-dot.online { background: #4CAF50; box-shadow: 0 0 10px #4CAF50; }
        .status-dot.offline { background: #f44336; }
        .status-dot.mock { background: #ff9800; }
        .channel-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; }
        .channel { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 12px; text-align: center; }
        .channel h3 { font-size: 0.9rem; opacity: 0.9; margin-bottom: 8px; }
        .channel .value { font-size: 1.8rem; font-weight: bold; }
        .channel .voltage { font-size: 1.2rem; opacity: 0.9; }
        .btn { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; padding: 12px 24px; border-radius: 8px; cursor: pointer; font-size: 1rem; transition: transform 0.2s, box-shadow 0.2s; margin: 5px; }
        .btn:hover { transform: translateY(-2px); box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4); }
        .btn.secondary { background: #f5f5f5; color: #333; }
        .btn.success { background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); }
        .btn.warning { background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%); }
        .i2c-table { width: 100%; border-collapse: collapse; margin-top: 15px; }
        .i2c-table th, .i2c-table td { padding: 12px; text-align: left; border-bottom: 1px solid #e0e0e0; }
        .i2c-table th { background: #f5f5f5; font-weight: 600; color: #333; }
        .badge { padding: 4px 12px; border-radius: 20px; font-size: 0.8rem; font-weight: 600; }
        .badge.success { background: #e8f5e9; color: #4CAF50; }
        .badge.warning { background: #fff3e0; color: #ff9800; }
        .config-form { display: grid; gap: 15px; }
        .form-group { display: flex; flex-direction: column; gap: 5px; }
        .form-group label { font-weight: 600; color: #555; font-size: 0.9rem; }
        .form-group input, .form-group select { padding: 10px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 1rem; }
        .form-group input:focus, .form-group select:focus { outline: none; border-color: #667eea; }
        .log { background: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 8px; font-family: 'Courier New', monospace; font-size: 0.85rem; max-height: 200px; overflow-y: auto; margin-top: 15px; }
        .log .timestamp { color: #6cc644; }
        .log .info { color: #79b8ff; }
        .log .error { color: #f97583; }
        .live-indicator { display: inline-flex; align-items: center; gap: 8px; padding: 8px 16px; background: #e8f5e9; border-radius: 20px; color: #4CAF50; font-weight: 600; font-size: 0.9rem; }
        .live-indicator::before { content: ""; width: 8px; height: 8px; background: #4CAF50; border-radius: 50%; animation: pulse 1s infinite; }
        @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
        .hidden { display: none !important; }
    </style>
</head>
<body>
    <div class="container">
        <h1>🔌 piADC - ADS1115 Web Panel</h1>
        <div class="grid">
            <div class="card">
                <h2>📊 System Status</h2>
                <div id="status-content">
                    <div class="status">
                        <div class="status-dot offline" id="status-dot"></div>
                        <span id="status-text">Checking...</span>
                    </div>
                    <div class="status"><span>🔄 Mode:</span><span id="mode-text">-</span></div>
                    <div class="status"><span>📍 I2C Address:</span><span id="i2c-addr">-</span></div>
                    <div class="status"><span>⚙️ Gain:</span><span id="gain-val">-</span></div>
                    <div class="status"><span>📈 Data Rate:</span><span id="data-rate">-</span></div>
                </div>
                <div style="margin-top: 20px;">
                    <button class="btn" onclick="refreshStatus()">🔄 Refresh</button>
                    <button class="btn secondary" onclick="resetService()">🔄 Reset</button>
                </div>
            </div>
            <div class="card">
                <h2>🔍 I2C Scanner</h2>
                <div id="i2c-content"><p>Click scan to detect I2C devices.</p></div>
                <div style="margin-top: 15px;">
                    <button class="btn" onclick="scanI2C()">🔍 Scan I2C</button>
                </div>
                <div id="i2c-results" class="hidden" style="margin-top: 15px;">
                    <table class="i2c-table">
                        <thead><tr><th>Address</th><th>Device</th><th>Status</th></tr></thead>
                        <tbody id="i2c-table-body"></tbody>
                    </table>
                </div>
            </div>
            <div class="card" style="grid-column: 1 / -1;">
                <h2>📈 ADC Channel Readings</h2>
                <div style="margin-bottom: 20px;">
                    <button class="btn" onclick="readAll()">📊 Read All</button>
                    <button class="btn success" id="live-btn" onclick="toggleLive()">▶️ Live</button>
                    <button class="btn warning" onclick="clearData()">🗑️ Clear</button>
                    <span id="live-indicator" class="live-indicator hidden">LIVE</span>
                </div>
                <div class="channel-grid" id="channels">
                    <div class="channel"><h3>Channel 0</h3><div class="value" id="ch0-raw">-</div><div class="voltage" id="ch0-v">-</div></div>
                    <div class="channel"><h3>Channel 1</h3><div class="value" id="ch1-raw">-</div><div class="voltage" id="ch1-v">-</div></div>
                    <div class="channel"><h3>Channel 2</h3><div class="value" id="ch2-raw">-</div><div class="voltage" id="ch2-v">-</div></div>
                    <div class="channel"><h3>Channel 3</h3><div class="value" id="ch3-raw">-</div><div class="voltage" id="ch3-v">-</div></div>
                </div>
                <div class="log" id="log"><div><span class="timestamp">[START]</span> Panel initialized</div></div>
            </div>
            <div class="card" style="grid-column: 1 / -1;">
                <h2>⚙️ Configuration</h2>
                <div class="config-form">
                    <div class="form-group">
                        <label for="cfg-address">I2C Address</label>
                        <select id="cfg-address">
                            <option value="0x48">0x48 (GND)</option>
                            <option value="0x49">0x49 (VDD)</option>
                            <option value="0x4A">0x4A (SDA)</option>
                            <option value="0x4B">0x4B (SCL)</option>
                        </select>
                    </div>
                    <div class="form-group">
                        <label for="cfg-gain">Gain</label>
                        <select id="cfg-gain">
                            <option value="0.6667">2/3 (±6.144V)</option>
                            <option value="1" selected>1 (±4.096V)</option>
                            <option value="2">2 (±2.048V)</option>
                            <option value="4">4 (±1.024V)</option>
                            <option value="8">8 (±0.512V)</option>
                            <option value="16">16 (±0.256V)</option>
                        </select>
                    </div>
                    <div class="form-group">
                        <label for="cfg-rate">Data Rate (SPS)</label>
                        <select id="cfg-rate">
                            <option value="8">8</option>
                            <option value="16">16</option>
                            <option value="32">32</option>
                            <option value="64">64</option>
                            <option value="128" selected>128</option>
                            <option value="250">250</option>
                            <option value="475">475</option>
                            <option value="860">860</option>
                        </select>
                    </div>
                    <div class="form-group">
                        <label for="cfg-mode">Mode</label>
                        <select id="cfg-mode">
                            <option value="single" selected>Single-Shot</option>
                            <option value="continuous">Continuous</option>
                        </select>
                    </div>
                    <div><button class="btn" onclick="updateConfig()">💾 Save Configuration</button></div>
                </div>
            </div>
        </div>
    </div>
    <script>
        let ws = null, liveInterval = null, isLive = false;
        function log(msg, type = 'info') {
            const logEl = document.getElementById('log');
            const timestamp = new Date().toLocaleTimeString();
            const div = document.createElement('div');
            div.innerHTML = `<span class="timestamp">[${timestamp}]</span> <span class="${type}">${msg}</span>`;
            logEl.insertBefore(div, logEl.firstChild);
        }
        async function refreshStatus() {
            try {
                const resp = await fetch('/api/health');
                const data = await resp.json();
                const dot = document.getElementById('status-dot');
                const text = document.getElementById('status-text');
                if (data.initialized) {
                    dot.className = data.mock_mode ? 'status-dot mock' : 'status-dot online';
                    text.textContent = data.mock_mode ? 'Mock Mode' : 'Connected';
                } else {
                    dot.className = 'status-dot offline';
                    text.textContent = 'Not Initialized';
                }
                const cfgResp = await fetch('/api/config');
                const cfg = await cfgResp.json();
                document.getElementById('mode-text').textContent = cfg.mock_mode ? 'Mock' : 'Hardware';
                document.getElementById('i2c-addr').textContent = cfg.i2c_address;
                document.getElementById('gain-val').textContent = cfg.gain;
                document.getElementById('data-rate').textContent = cfg.data_rate + ' SPS';
                document.getElementById('cfg-address').value = cfg.i2c_address;
                document.getElementById('cfg-gain').value = cfg.gain.toString();
                document.getElementById('cfg-rate').value = cfg.data_rate.toString();
                document.getElementById('cfg-mode').value = cfg.mode;
            } catch (e) { log('Failed to refresh status: ' + e.message, 'error'); }
        }
        async function scanI2C() {
            log('Scanning I2C bus...', 'info');
            try {
                const resp = await fetch('/api/i2c/scan');
                const data = await resp.json();
                const resultsDiv = document.getElementById('i2c-results');
                const tbody = document.getElementById('i2c-table-body');
                tbody.innerHTML = '';
                if (data.devices.length === 0) {
                    tbody.innerHTML = '<tr><td colspan="3" style="text-align: center;">No devices found</td></tr>';
                } else {
                    data.devices.forEach(dev => {
                        const row = document.createElement('tr');
                        const isAds = dev.description.includes('ADS1115');
                        row.innerHTML = `<td><code>${dev.address}</code></td><td>${dev.description}</td><td><span class="badge ${isAds ? 'success' : 'warning'}">${isAds ? 'ADS1115 ✓' : 'Detected'}</span></td>`;
                        tbody.appendChild(row);
                    });
                }
                resultsDiv.classList.remove('hidden');
                if (data.ads1115_detected) log(`ADS1115 detected at ${data.ads1115_address}!`, 'success');
                else log('ADS1115 not detected', 'warning');
            } catch (e) { log('I2C scan failed: ' + e.message, 'error'); }
        }
        async function readAll() {
            try {
                const resp = await fetch('/api/read/all');
                const data = await resp.json();
                updateChannels(data);
                log(`Read completed (mock=${data.mock_mode})`, 'success');
            } catch (e) { log('Read failed: ' + e.message, 'error'); }
        }
        function updateChannels(data) {
            data.channels.forEach((ch, i) => {
                document.getElementById(`ch${i}-raw`).textContent = ch.raw_value;
                document.getElementById(`ch${i}-v`).textContent = ch.voltage.toFixed(6) + 'V';
            });
        }
        function toggleLive() {
            const btn = document.getElementById('live-btn');
            const indicator = document.getElementById('live-indicator');
            if (isLive) {
                isLive = false;
                if (ws) { ws.send(JSON.stringify({action: 'stop_live'})); ws.close(); ws = null; }
                btn.textContent = '▶️ Live'; btn.classList.remove('warning'); indicator.classList.add('hidden');
                log('Live mode stopped');
            } else {
                isLive = true;
                btn.textContent = '⏹️ Stop'; btn.classList.add('warning'); indicator.classList.remove('hidden');
                liveInterval = setInterval(readAll, 1000);
                log('Live mode started (1s interval)', 'success');
            }
        }
        function clearData() {
            for (let i = 0; i < 4; i++) {
                document.getElementById(`ch${i}-raw`).textContent = '-';
                document.getElementById(`ch${i}-v`).textContent = '-';
            }
            log('Data cleared');
        }
        async function updateConfig() {
            const config = {
                i2c_address: document.getElementById('cfg-address').value,
                gain: parseFloat(document.getElementById('cfg-gain').value),
                data_rate: parseInt(document.getElementById('cfg-rate').value),
                mode: document.getElementById('cfg-mode').value
            };
            try {
                const resp = await fetch('/api/config', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(config) });
                const data = await resp.json();
                log('Configuration saved!', 'success');
                refreshStatus();
            } catch (e) { log('Failed to save config: ' + e.message, 'error'); }
        }
        async function resetService() {
            try {
                await fetch('/api/reset', {method: 'POST'});
                log('Service reset', 'info');
                setTimeout(refreshStatus, 1000);
            } catch (e) { log('Reset failed: ' + e.message, 'error'); }
        }
        refreshStatus();
        setInterval(refreshStatus, 5000);
    </script>
</body>
</html>'''


def main():
    """Run web panel server."""
    import uvicorn
    import os
    host = os.getenv("API_HOST", "0.0.0.0")
    port = int(os.getenv("API_PORT", "8080"))
    print(f"Starting piADC Web Panel at http://{host}:{port}")
    uvicorn.run(app, host=host, port=port)


if __name__ == "__main__":
    main()
