import { state } from './state.js';
import { getFromDB, clearStore, addItemsToStore } from './db.js';
import { logToConsole, emitirEventoActualizacion, mostrarSpinner, ocultarSpinner, showNotification, fetchWithTimeout, TimeoutError, _ } from './utils.js';

function mapSectionTypeToObjectName(sectionType) {
    switch (sectionType) {
        case 'movie': return 'movies';
        case 'show': return 'series';
        case 'artist': return 'artists';
        case 'photo': return 'photos';
        default: return null;
    }
}

async function fetchPlexResources(token, signal) {
    const response = await fetchWithTimeout(`https://plex.tv/api/resources?X-Plex-Token=${token}`, { signal }, 10000);
    if (!response.ok) throw new Error(`Error ${response.status} obteniendo recursos (token ${token.substring(0, 4)})`);
    const data = await response.text();
    const parser = new DOMParser();
    const xmlDoc = parser.parseFromString(data, "application/xml");
    if (xmlDoc.querySelector('parsererror')) throw new Error('Error parseando XML de recursos Plex');
    return xmlDoc.querySelectorAll('Device[product="Plex Media Server"]');
}

async function fetchSectionContent(url, signal, timeout = 7000) {
    const response = await fetchWithTimeout(url, { signal }, timeout);
    if (!response.ok) throw new Error(`Error ${response.status} obteniendo contenido`);
    const data = await response.text();
    const parser = new DOMParser();
    const xmlDoc = parser.parseFromString(data, "text/xml");
    if (xmlDoc.querySelector('parsererror')) throw new Error('Error parseando XML de contenido');
    return xmlDoc;
}

async function parseAndStoreSectionItems(contentXml, storeName, serverData) {
    const type = storeName === 'movies' ? 'movie' : (storeName === 'series' ? 'show' : (storeName === 'artists' ? 'artist' : 'photo'));
    let items;

    if (type === 'movie' || type === 'show') {
        const itemSelector = type === 'movie' ? 'Video' : 'Directory';
        items = Array.from(contentXml.querySelectorAll(itemSelector)).map(el => ({
            title: el.getAttribute('title'),
            year: el.getAttribute('year'),
            genre: el.querySelector('Genre')?.getAttribute('tag') || _('noGenre')
        }));
    } else if (type === 'artist' || type === 'photo') {
        items = Array.from(contentXml.querySelectorAll('Directory')).map(el => ({
            id: el.getAttribute('ratingKey'),
            title: el.getAttribute('title'),
            thumb: el.getAttribute('thumb')
        }));
    }

    const validItems = items.filter(i => i && i.title);
    logToConsole(`       Encontrados ${validItems.length} items válidos en "${serverData.sectionTitle}".`);
    if (validItems.length > 0) {
        const entry = {
            ip: serverData.ip,
            puerto: serverData.puerto,
            token: serverData.token,
            protocolo: serverData.protocolo,
            serverName: serverData.nombre,
            titulos: validItems,
            tokenPrincipal: serverData.tokenPrincipal
        };
        await addItemsToStore(storeName, [entry]);
    }
}

async function processServer(device, token, tipos, signal) {
    const serverName = device.getAttribute('name') || 'Servidor Sin Nombre';
    const accessToken = device.getAttribute('accessToken');
    if (!accessToken) {
        throw new Error(`Servidor: ${serverName} - Sin token de acceso.`);
    }

    const connections = Array.from(device.querySelectorAll('Connection'));
    const connection = connections.find(c => c.getAttribute('local') === '0') || connections.find(c => c.getAttribute('local') === '1');
    if (!connection) {
        throw new Error(`Servidor: ${serverName} - Sin conexión válida.`);
    }

    const protocol = connection.getAttribute('protocol');
    const address = connection.getAttribute('address');
    const port = connection.getAttribute('port');
    if (!address || !port) {
        throw new Error(`Servidor: ${serverName} - IP o Puerto no encontrados.`);
    }
    
    logToConsole(` -> Procesando servidor: ${serverName} (${address}:${port})`);
    
    const connectionData = { ip: address, puerto: port, token: accessToken, protocolo: protocol, tokenPrincipal: token, nombre: serverName };
    await addItemsToStore('conexiones_locales', [connectionData]);

    const sectionsUrl = `${protocol}://${address}:${port}/library/sections?X-Plex-Token=${accessToken}`;
    const sectionsXml = await fetchSectionContent(sectionsUrl, signal);
    const directories = sectionsXml.querySelectorAll('Directory');
    logToConsole(`   Encontradas ${directories.length} secciones en ${serverName}.`);

    const sectionPromises = Array.from(directories).map(async (dir) => {
        const type = dir.getAttribute('type');
        const storeName = mapSectionTypeToObjectName(type);
        if (storeName && tipos.includes(storeName)) {
            const sectionId = dir.getAttribute('key');
            const sectionTitle = dir.getAttribute('title');
            logToConsole(`     Procesando sección: "${sectionTitle}" (Tipo: ${type})`);
            const contentUrl = `${protocol}://${address}:${port}/library/sections/${sectionId}/all?X-Plex-Token=${accessToken}`;
            try {
                const contentXml = await fetchSectionContent(contentUrl, signal);
                const serverData = { ...connectionData, sectionTitle };
                await parseAndStoreSectionItems(contentXml, storeName, serverData);
            } catch (e) {
                if (e instanceof TimeoutError) {
                    logToConsole(`     [REINTENTO PENDIENTE] La sección "${sectionTitle}" tardó demasiado en responder.`);
                    state.plexScanRetryQueue.push({
                        url: contentUrl,
                        storeName: storeName,
                        serverData: { ...connectionData, sectionTitle }
                    });
                } else {
                    logToConsole(`     Error procesando sección "${sectionTitle}": ${e.message}`);
                }
            }
        }
    });
    await Promise.all(sectionPromises);
}

async function processRetryQueue(signal) {
    if (state.plexScanRetryQueue.length === 0) {
        logToConsole(_('noRetriesPending'));
        return;
    }

    logToConsole(_('startingRetryPhase', state.plexScanRetryQueue.length));
    const queue = [...state.plexScanRetryQueue];
    state.plexScanRetryQueue = []; 

    for (const item of queue) {
        if (signal.aborted) {
            logToConsole(_('retryPhaseCancelled'));
            break;
        }
        const { url, storeName, serverData } = item;
        logToConsole(_('retyingSection', serverData.sectionTitle));
        try {
            const contentXml = await fetchSectionContent(url, signal, 30000); 
            await parseAndStoreSectionItems(contentXml, storeName, serverData);
            logToConsole(_('retrySuccess', serverData.sectionTitle));
        } catch (e) {
            logToConsole(_('retryError', [serverData.sectionTitle, e.message]));
        }
    }

    logToConsole(_('retryPhaseFinished'));
}

export async function startPlexScan(tipos) {
    if (state.isScanningPlex) {
        showNotification(_('plexScanInProgress'), "warning");
        return;
    }
    state.isScanningPlex = true;
    state.plexScanAbortController = new AbortController();
    state.plexScanRetryQueue = [];
    const signal = state.plexScanAbortController.signal;
    
    mostrarSpinner();
    logToConsole(_('plexScanStarting'));

    try {
        const tokensData = await getFromDB('tokens');
        const tokens = tokensData.map(item => item.token).filter(Boolean);
        if (tokens.length === 0) throw new Error(_('noPlexTokens'));

        logToConsole(_('clearingSections', tipos.join(', ')));
        await Promise.all(tipos.map(tipo => clearStore(tipo)));
        await clearStore('conexiones_locales');
        logToConsole(_('sectionsCleared'));
        
        const tokenPromises = tokens.map(async (token) => {
            if (signal.aborted) return;
            try {
                const devices = await fetchPlexResources(token, signal);
                logToConsole(_('tokenFoundServers', [token.substring(0,4), devices.length]));
                
                const serverPromises = Array.from(devices).map(device => processServer(device, token, tipos, signal));
                await Promise.allSettled(serverPromises);

            } catch (e) {
                logToConsole(_('errorProcessingToken', [token.substring(0,4), e.message]));
            }
        });

        await Promise.all(tokenPromises);
        
        logToConsole(_('initialScanPhaseComplete'));

        await processRetryQueue(signal);

        logToConsole(_('plexScanFinished'));
        showNotification(_('plexScanFinished'), 'success');
        emitirEventoActualizacion();

    } catch (error) {
        if (error.name !== 'AbortError') {
            logToConsole(_('plexScanFatalError', error.message));
            showNotification(_('errorDuringScan', error.message), 'error');
        } else {
            logToConsole(_('scanCancelled'));
            showNotification(_('scanCancelledInfo'), 'info');
        }
    } finally {
        state.isScanningPlex = false;
        state.plexScanAbortController = null;
        ocultarSpinner();
    }
}