from fastapi import APIRouter, HTTPException, Depends, Query
from pydantic import BaseModel, Field
from typing import Optional
import logging
import datetime
from dbmodel import Study, Action, ActionProperty, ActionPropertyType, ActionProperty_String, Person, Client, Event, samson_db
from peewee import JOIN
from ..dependencies import get_db
import config

logger = logging.getLogger("sampacs")

router = APIRouter(prefix="/api/descriptions", tags=["descriptions"])


def get_description_property_name() -> str:
    """Get description property name from config"""
    return config.config.description_property_name


def get_conclusion_property_name() -> str:
    """Get conclusion property name from config"""
    return config.config.conclusion_property_name

class ClientInfo(BaseModel):
    """Client information model"""
    id: int = Field(..., description="ID клиента")
    last_name: str = Field(..., description="Фамилия")
    first_name: str = Field(..., description="Имя")
    patr_name: str = Field(..., description="Отчество")
    sex: int = Field(..., description="Пол (0-женский, 1-мужской)")
    birth_date: Optional[datetime] = Field(None, description="Дата рождения")

    class Config:
        exclude_none = True

class StudyDescriptionSaveRequest(BaseModel):
    """Request model for saving study description"""
    description_property_id: Optional[int] = Field(None, description="ID свойства описания в ActionProperty")
    conclusion_property_id: Optional[int] = Field(None, description="ID свойства заключения в ActionProperty")
    description: Optional[str] = Field(None, description="Описание исследования")
    conclusion: Optional[str] = Field(None, description="Заключение по исследованию")

class StudyDescriptionResponse(BaseModel):
    """Response model for study description"""
    study_uid: str = Field(..., description="Study Instance UID")
    description: Optional[str] = Field(None, description="Описание исследования")
    conclusion: Optional[str] = Field(None, description="Заключение по исследованию")
    description_property_id: Optional[int] = Field(None, description="ID свойства описания в ActionProperty")
    conclusion_property_id: Optional[int] = Field(None, description="ID свойства заключения в ActionProperty")
    end_date: Optional[datetime] = Field(None, description="Дата закрытия действия")
    person: Optional[str] = Field(None, description="ФИО ответственного врача")
    action_status: Optional[int] = Field(None, description="Статус действия из таблицы Action")
    client: Optional[ClientInfo] = Field(None, description="Информация о пациенте")

@router.get(
    "/{study_uid}",
    summary="Получить описание исследования",
    response_description="Описание и заключение исследования",
    responses={
        200: {
            "description": "Описание исследования успешно получено",
            "content": {
                "application/json": {
                    "example": {
                        "study_uid": "1.2.840.113619.2.5.1762583153.215519.978957063.78",
                        "description": "КТ органов грудной клетки",
                        "conclusion": "Признаки пневмонии в нижней доле правого легкого",
                        "description_property_id": 456,
                        "conclusion_property_id": 789,
                        "end_date": "2024-01-15T11:45:00",
                        "person": "Иванов Иван Иванович",
                        "action_status": 2,
                        "client": {
                            "id": 123,
                            "last_name": "Петров",
                            "first_name": "Петр",
                            "patr_name": "Петрович",
                            "sex": 1,
                            "birth_date": "1980-05-15"
                        }
                    }
                }
            }
        },
        404: {"description": "Исследование не найдено"},
        500: {"description": "Внутренняя ошибка сервера"}
    }
)
async def get_study_description(
    study_uid: str,
    db=Depends(get_db)
) -> StudyDescriptionResponse:
    """
    Получить описание и заключение для исследования по его UID.

    ## Параметры:
    - **study_uid**: Study Instance UID исследования

    ## Возвращает:
    - Объект с описанием исследования, заключением и метаданными

    ## Ошибки:
    - 404: Исследование с указанным UID не найдено
    - 500: Внутренняя ошибка сервера при получении данных
    """
    try:
        # Step 1: Get Study record from PACS database
        study = Study.get_or_none(Study.uid == study_uid)
        if not study:
            raise HTTPException(status_code=404, detail="Study not found")

        # Check accession_number field
        accession_number = study.accession_number
        if not accession_number:
            raise HTTPException(
                status_code=400,
                detail="Study has no accession_number"
            )

        # Convert string to integer
        try:
            action_id = int(accession_number)
        except ValueError:
            raise HTTPException(
                status_code=400,
                detail=f"Invalid accession_number format: {accession_number}"
            )

        # Step 2: Connect to samson_db and get Action record with Person and Client joins
        samson_db.connect()
        try:
            action = (Action
                     .select(Action, Person, Client)
                     .join(Person, JOIN.LEFT_OUTER, on=(Action.person_id == Person.id))
                     .join(Event, JOIN.INNER, on=(Action.event_id == Event.id))
                     .join(Client, JOIN.INNER, on=(Event.client_id == Client.id))
                     .where(Action.id == action_id)
                     .first())
            if not action:
                raise HTTPException(
                    status_code=404,
                    detail=f"Action with id {action_id} not found"
                )

            # Get property names from config
            desc_prop_name = get_description_property_name()
            concl_prop_name = get_conclusion_property_name()

            # Initialize response values
            description = None
            conclusion = None
            description_property_id = None
            conclusion_property_id = None
            end_date = action.endDate
            person = None
            action_status = action.status

            client = action.event.client
            client_info = ClientInfo(
                id=client.id,
                last_name=client.lastName or "",
                first_name=client.firstName or "",
                patr_name=client.patrName or "",
                sex=client.sex,
                birth_date=client.birthDate
            )

            # Get description property
            desc_prop = (ActionProperty_String
                        .select(ActionProperty_String, ActionProperty, ActionPropertyType)
                        .join(ActionProperty, on=(ActionProperty.id == ActionProperty_String.id))
                        .join(ActionPropertyType, on=(ActionProperty.type == ActionPropertyType.id))
                        .where(
                            ActionProperty.action_id == action.id,
                            ActionProperty.deleted == 0,
                            ActionPropertyType.name == desc_prop_name,
                            ActionPropertyType.deleted == 0
                        )
                        .first())

            if desc_prop:
                description = desc_prop.value
                description_property_id = desc_prop.actionproperty.id

            # Get conclusion property
            concl_prop = (ActionProperty_String
                         .select(ActionProperty_String, ActionProperty, ActionPropertyType)
                         .join(ActionProperty, on=(ActionProperty.id == ActionProperty_String.id))
                         .join(ActionPropertyType, on=(ActionProperty.type == ActionPropertyType.id))
                         .where(
                             ActionProperty.action_id == action.id,
                             ActionProperty.deleted == 0,
                             ActionPropertyType.name == concl_prop_name,
                             ActionPropertyType.deleted == 0
                         )
                         .first())

            if concl_prop:
                conclusion = concl_prop.value
                conclusion_property_id = concl_prop.actionproperty.id

            # Get person from joined Person if exists
            if action.person_id:
                person_obj = action.person
                if person_obj:
                    person = f"{person_obj.lastName or ''} {person_obj.firstName or ''} {person_obj.patrName or ''}".strip()

            return StudyDescriptionResponse(
                study_uid=study_uid,
                description=description,
                conclusion=conclusion,
                description_property_id=description_property_id,
                conclusion_property_id=conclusion_property_id,
                end_date=end_date,
                person=person,
                action_status=action_status,
                client=client_info
            )

        finally:
            if not samson_db.is_closed():
                samson_db.close()

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Error getting description for study {study_uid}: {str(e)}", exc_info=True)
        raise HTTPException(status_code=500, detail="Internal server error while getting description")

@router.put(
    "/{study_uid}",
    summary="Сохранить описание исследования",
    response_description="Результат сохранения описания",
    responses={
        200: {
            "description": "Описание успешно сохранено",
            "content": {
                "application/json": {
                    "example": {
                        "success": True,
                        "message": "Описание успешно сохранено",
                        "study_uid": "1.2.840.113619.2.5.1762583153.215519.978957063.78"
                    }
                }
            }
        },
        400: {"description": "Неверные данные запроса"},
        404: {"description": "Исследование не найдено"},
        500: {"description": "Внутренняя ошибка сервера при сохранении"}
    }
)
async def save_study_description(
    study_uid: str,
    description_data: StudyDescriptionSaveRequest,
    db=Depends(get_db)
) -> dict:
    """
    Сохранить описание и заключение для исследования.

    ## Параметры:
    - **study_uid**: Study Instance UID исследования
    - **description_data**: Данные описания (описание и заключение)

    ## Возвращает:
    - Объект с результатом операции

    ## Ошибки:
    - 400: Не указаны данные для сохранения
    - 404: Исследование с указанным UID не найдено
    - 500: Внутренняя ошибка сервера при сохранении данных
    """
    try:
        # Проверяем, что есть данные для сохранения
        # Validate that for each value there is a corresponding property_id
        has_description = description_data.description is not None
        has_conclusion = description_data.conclusion is not None
        has_desc_prop_id = description_data.description_property_id is not None
        has_concl_prop_id = description_data.conclusion_property_id is not None

        # Check if we have at least one pair to save
        if not ((has_description and has_desc_prop_id) or (has_conclusion and has_concl_prop_id)):
            raise HTTPException(
                status_code=400,
                detail="No valid data provided for saving. For each value (description/conclusion) provide corresponding property_id"
            )

        # Check individual pairs
        if has_description and not has_desc_prop_id:
            raise HTTPException(
                status_code=400,
                detail="Description provided but description_property_id is missing"
            )

        if has_conclusion and not has_concl_prop_id:
            raise HTTPException(
                status_code=400,
                detail="Conclusion provided but conclusion_property_id is missing"
            )
            raise HTTPException(
                status_code=400,
                detail="No data provided for saving. Specify at least one of the fields: description or conclusion with corresponding property IDs"
            )

        # Step 1: Get Study record from PACS database
        study = Study.get_or_none(Study.uid == study_uid)
        if not study:
            raise HTTPException(status_code=404, detail="Study not found")

        # Check accession_number field
        accession_number = study.accession_number
        if not accession_number:
            raise HTTPException(
                status_code=400,
                detail="Study has no accession_number"
            )

        # Convert string to integer
        try:
            action_id = int(accession_number)
        except ValueError:
            raise HTTPException(
                status_code=400,
                detail=f"Invalid accession_number format: {accession_number}"
            )

        # Step 2: Connect to samson_db and implement saving logic
        samson_db.connect()
        try:
            # Get Action with Event for locking
            action = (Action
                     .select(Action, Event)
                     .join(Event, JOIN.INNER, on=(Action.event_id == Event.id))
                     .where(Action.id == action_id)
                     .first())

            if not action:
                raise HTTPException(
                    status_code=404,
                    detail=f"Action with id {action_id} not found"
                )

            event_id = action.event.id

            # Initialize variables for locking
            lock_id = None
            is_success = False

            try:
                # Lock the event
                samson_db.execute_sql('CALL getAppLock_prepare()')
                lock_query = samson_db.execute_sql(
                    f"CALL getAppLock_('Event', {event_id}, 0, 1, 'pacs', @res)"
                )

                # Get the result
                result_query = samson_db.execute_sql("SELECT @res")
                result_record = result_query.fetchone()

                if result_record and result_record[0]:
                    result_parts = str(result_record[0]).split()
                    if len(result_parts) > 1:
                        is_success = int(result_parts[0])
                        if is_success:
                            lock_id = int(result_parts[1])
                        else:
                            # Get lock information
                            lock_id = int(result_parts[1])
                            try:
                                # Query AppLock table for lock details
                                lock_query = samson_db.execute_sql(
                                    f"SELECT lockTime, person_id, addr FROM AppLock WHERE id = {lock_id}"
                                )
                                lock_record = lock_query.fetchone()

                                if lock_record:
                                    lock_time = lock_record[0]
                                    person_id = lock_record[1]
                                    addr = lock_record[2] if lock_record[2] else "unknown"

                                    # Get person name
                                    person_name = "аноним"
                                    if person_id:
                                        person_query = samson_db.execute_sql(
                                            f"SELECT name FROM vrbPersonWithSpeciality WHERE id = {person_id}"
                                        )
                                        person_record = person_query.fetchone()
                                        if person_record and person_record[0]:
                                            person_name = person_record[0]

                                    lock_details = {
                                        "event_id": event_id,
                                        "locked_by": person_name,
                                        "lock_time": str(lock_time),
                                        "lock_address": addr,
                                        "lock_id": lock_id
                                    }
                                else:
                                    lock_details = {
                                        "event_id": event_id,
                                        "locked_by": "another process",
                                        "lock_id": lock_id
                                    }

                                raise HTTPException(
                                    status_code=423,
                                    detail=lock_details
                                )
                            except HTTPException as e:
                                raise e
                            except Exception as e:
                                raise HTTPException(
                                    status_code=423,
                                    detail={
                                        "event_id": event_id,
                                        "locked_by": "another process",
                                        "lock_id": lock_id
                                    }
                                )

                if not is_success or not lock_id:
                    raise HTTPException(
                        status_code=500,
                        detail="Failed to acquire lock for event"
                    )

                # Now we have the lock, proceed with saving
                updates_made = False

                # Save description if provided
                if description_data.description_property_id and description_data.description is not None:
                    # Verify property belongs to correct action
                    desc_prop = (ActionProperty_String
                                .select(ActionProperty_String, ActionProperty)
                                .join(ActionProperty, on=(ActionProperty.id == ActionProperty_String.id))
                                .where(
                                    ActionProperty_String.id == description_data.description_property_id,
                                    ActionProperty.action_id == action_id,
                                    ActionProperty_String.value == ""
                                )
                                .first())

                    if not desc_prop:
                        raise HTTPException(
                            status_code=400,
                            detail="Description property not found, already has value, or doesn't belong to this action"
                        )

                    # Update description
                    desc_prop.value = description_data.description
                    desc_prop.save()
                    updates_made = True

                # Save conclusion if provided
                if description_data.conclusion_property_id and description_data.conclusion is not None:
                    # Verify property belongs to correct action
                    concl_prop = (ActionProperty_String
                                 .select(ActionProperty_String, ActionProperty)
                                 .join(ActionProperty, on=(ActionProperty.id == ActionProperty_String.id))
                                 .where(
                                     ActionProperty_String.id == description_data.conclusion_property_id,
                                     ActionProperty.action_id == action_id,
                                     ActionProperty_String.value == ""
                                 )
                                 .first())

                    if not concl_prop:
                        raise HTTPException(
                            status_code=400,
                            detail="Conclusion property not found, already has value, or doesn't belong to this action"
                        )

                    # Update conclusion
                    concl_prop.value = description_data.conclusion
                    concl_prop.save()
                    updates_made = True

                if not updates_made:
                    raise HTTPException(
                        status_code=400,
                        detail="No valid updates to perform"
                    )

                # Update ActionProperty modifyDatetime
                if description_data.description_property_id:
                    ap = ActionProperty.get_by_id(description_data.description_property_id)
                    ap.modifyDatetime = datetime.now()
                    ap.save()

                if description_data.conclusion_property_id:
                    ap = ActionProperty.get_by_id(description_data.conclusion_property_id)
                    ap.modifyDatetime = datetime.now()
                    ap.save()

                return {
                    "success": True,
                    "description_updated": description_data.description is not None,
                    "conclusion_updated": description_data.conclusion is not None
                }

            finally:
                # Always release the lock if we acquired it
                if lock_id:
                    samson_db.execute_sql(f"CALL ReleaseAppLock({lock_id})")

        except HTTPException:
            raise
        except Exception as e:
            logger.error(f"Error saving description for study {study_uid}: {str(e)}", exc_info=True)
            raise HTTPException(status_code=500, detail="Internal server error while saving description")
        finally:
            if not samson_db.is_closed():
                samson_db.close()

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Error saving description for study {study_uid}: {str(e)}", exc_info=True)
        raise HTTPException(status_code=500, detail="Internal server error while saving description")
