import logging
import os
import tempfile
from typing import Dict, List, Optional

from fastapi import FastAPI
from prometheus_client import Histogram, generate_latest, Counter
from prometheus_client.exposition import CONTENT_TYPE_LATEST
import time
from fastapi.responses import JSONResponse, Response
from pydantic import BaseModel
from pymilvus import connections

from src.DiagramProcessorFactory import DiagramProcessorFactory
from src.parsed_xml_to_milvus import (
    connect_to_milvus,
    create_collection,
    json_to_text_schema,
    load_env,
    prepare_chunks,
    upsert_tables_data,
)

class MetricsLogFilter(logging.Filter):
    """Фильтр для исключения логов метрик из access логов"""
    
    def filter(self, record):
        if hasattr(record, 'args') and record.args and len(record.args) >= 3:
            request_path = record.args[2]
            if '/metrics' in request_path:
                return False
        return True

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S',
)
logger = logging.getLogger(__name__)

uvicorn_access_logger = logging.getLogger("uvicorn.access")
uvicorn_access_logger.addFilter(MetricsLogFilter())

app = FastAPI()

PROCESSING_TIME = Histogram(
    "xml_document_processing_seconds",
    "Время обработки файла в секундах",
    buckets=(0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0)
)
PROCESSING_COUNT = Counter(
    "xml_document_processing_count",
    "Количество обработанных файлов",
    ["project_id", "status"]
)

class DocumentRequest(BaseModel):
    title: str
    id_external: str
    project_id: str
    content: str 

class SchemaProcessResponse(BaseModel):
    status: str
    message: str
    project_name: str
    tables_count: Optional[int] = None
    inserted_count: Optional[int] = None
    updated_count: Optional[int] = None
    skipped_count: Optional[int] = None
    detected_schema_type: Optional[str] = None


@app.get("/metrics")
async def metrics():
    return Response(generate_latest(), media_type=CONTENT_TYPE_LATEST)

@app.post('/process-schema', response_model=SchemaProcessResponse)
async def process_schema(request: DocumentRequest) -> SchemaProcessResponse:
    temp_xml_filename = None
    detected_schema_type = None
    parser = None
    processing_success = False

    start_time = time.time()
    try:
        logger.info(f'Начало обработки схемы. Проект: {request.project_id}')
        logger.info(f'Файл: {request.id_external}')
        load_env()

        with tempfile.NamedTemporaryFile(delete=False, suffix='.xml') as temp_file:
            temp_file.write(request.content.encode('utf-8'))
            temp_xml_filename = temp_file.name
            temp_file.flush()
        logger.debug(f'Временный файл создан: {temp_xml_filename}')

        logger.info('Попытка обработки файла как DrawIO схемы')
        try:
            drawio_parser = DiagramProcessorFactory.create_parser('drawio', file_path=temp_xml_filename)
            tables_dict = drawio_parser.get_tables_and_fields()

            if tables_dict and len(tables_dict) > 0:
                logger.info(f'DrawIO парсер успешно обнаружил {len(tables_dict)} таблиц')
                parser = drawio_parser
                detected_schema_type = 'drawio'
            else:
                logger.info('DrawIO парсер не обнаружил таблиц, пробуем DBeaver парсер')
        except Exception as error:
            logger.warning(f'Ошибка при использовании DrawIO парсера: {error}')

        if not parser:
            logger.info('Попытка обработки файла как DBeaver схемы')
            try:
                dbeaver_parser = DiagramProcessorFactory.create_parser('dbeaver', file_path=temp_xml_filename)
                tables_dict = dbeaver_parser.get_tables_and_fields()

                if tables_dict and len(tables_dict) > 0:
                    logger.info(f'DBeaver парсер успешно обнаружил {len(tables_dict)} таблиц')
                    parser = dbeaver_parser
                    detected_schema_type = 'dbeaver'
                else:
                    logger.warning('DBeaver парсер также не обнаружил таблиц')
            except Exception as error:
                logger.warning(f'Ошибка при использовании DBeaver парсера: {error}')

        if not parser:
            logger.error('Ни один из парсеров не смог обработать схему')
            return JSONResponse(
                status_code=400,
                content=SchemaProcessResponse(
                    status='error',
                    message='Не удалось распознать формат схемы. Файл не содержит распознаваемых таблиц.',
                    project_name=request.project_id,
                ).dict(),
            )

        tables_dict = parser.get_tables_and_fields()
        parsing_result = parser.parse_schema(tables_dict)
        logger.info(f'Таблицы успешно извлечены. Количество таблиц: {len(tables_dict)}')

        relationships = parser.get_table_relationships()
        logger.debug(f'Связи между таблицами извлечены')

        related_tables: Dict[str, List[str]] = {}
        for source, targets in relationships.items():
            if source not in related_tables:
                related_tables[source] = []

            for target in targets:
                if target not in related_tables[source]:
                    related_tables[source].append(target)

                if target not in related_tables:
                    related_tables[target] = []
                if source not in related_tables[target]:
                    related_tables[target].append(source)

        isolated_tables = parser.get_tables_without_relations()
        for table in isolated_tables:
            if table not in related_tables:
                related_tables[table] = []

        text_schema = json_to_text_schema(parsing_result)

        chunks = prepare_chunks(parsing_result, text_schema, related_tables, 
                                file_title=request.title, file_id=request.id_external)

        connect_to_milvus()

        collection = create_collection(request.project_id)

        try:
            inserted_count, updated_count, skipped_count = upsert_tables_data(collection, chunks)

            logger.info(
                f'Обработка данных завершена. Добавлено: {inserted_count}, обновлено: {updated_count}, пропущено: {skipped_count}',
            )
        except Exception as error:
            logger.error(f'Ошибка при вставке/обновлении данных: {error}')
            raise Exception(f'Ошибка при вставке/обновлении данных: {error}')
        finally:
            connections.disconnect(alias='default')

        processing_success = True

        return SchemaProcessResponse(
            status='success',
            message=f'Схема успешно обработана и индексирована (обнаружен тип: {detected_schema_type})',
            project_name=request.project_id,
            tables_count=len(tables_dict),
            inserted_count=inserted_count,
            updated_count=updated_count,
            skipped_count=skipped_count,
            detected_schema_type=detected_schema_type,
        )

    except Exception as error:
        logger.error(f'Ошибка при обработке: {str(error)}')
        return JSONResponse(
            status_code=500,
            content=SchemaProcessResponse(
                status='error',
                message=f'Ошибка при обработке: {str(error)}',
                project_name=request.project_id,
                detected_schema_type=detected_schema_type,
            ).dict(),
        )
    finally:
        PROCESSING_TIME.observe(time.time() - start_time)
        status = "success" if processing_success else "error"
        PROCESSING_COUNT.labels(project_id=request.project_id, status=status).inc()

        if temp_xml_filename and os.path.exists(temp_xml_filename):
            try:
                os.unlink(temp_xml_filename)
                logger.debug(f'Временный файл удален: {temp_xml_filename}')
            except:
                logger.warning(f'Не удалось удалить временный файл: {temp_xml_filename}')
                pass
