import httpx
import re
import sys
import json
import os
from loguru import logger
from typing import Dict, Any, Optional

# Configure logger
logger.remove()
logger.add(sys.stderr, format="<green>{time:HH:mm:ss}</green> | <level>{level: <8}</level> | <level>{message}</level>")

class SigaClient:
    """
    Asynchronous client for interacting with the Activesoft Siga API.
    """
    BASE_URL = "https://siga02.activesoft.com.br"
    LOGIN_PAGE_URL = f"{BASE_URL}/login/"
    API_ALUNOS_URL = f"{BASE_URL}/api/v1/alunos/"
    API_RESPONSAVEL_URL = f"{BASE_URL}/api/v1/responsavel/"
    API_RESPONSAVEL_DETALHE_URL_TEMPLATE = f"{BASE_URL}/api/v1/responsavel/crud/{{}}/"
    API_DETALHE_URL_TEMPLATE = f"{BASE_URL}/api/v1/alunos/crud/{{}}/"
    API_MENSAGEM_SAIDA_URL = f"{BASE_URL}/api/v1/mensagem_caixa_saida/"
    API_TURMAS_URL = f"{BASE_URL}/api/v1/turmas/"
    API_ALUNOTURMA_URL = f"{BASE_URL}/api/v1/alunoturma/"
    API_AVATAR_UPLOAD_URL_TEMPLATE = f"{BASE_URL}/api/v1/alunos/{{}}/avatar_upload/"
    API_BULK_FOTOS_URL = f"{BASE_URL}/api/v1/alunos/fotos/"
    API_RESPONSAVEL_BULK_FOTOS_URL = f"{BASE_URL}/api/v1/responsavel/fotos/"

    def __init__(self, username, password, instituicao, session_path: Optional[str] = None, remote_session_url: Optional[str] = None, verify: bool = True):
        self.username = username
        self.password = password
        self.instituicao = instituicao
        self.client = httpx.AsyncClient(follow_redirects=True, timeout=60.0, verify=verify)
        self.login_url = f"{self.LOGIN_PAGE_URL}?instituicao={instituicao}"
        self.csrf_token = None
        
        # Remote session sync settings
        # Priority: argument > environment variable > None
        env_remote_url = os.getenv("ACTIVESOFT_REMOTE_SESSION_URL")
        self.remote_session_url = (remote_session_url or env_remote_url).rstrip("/") if (remote_session_url or env_remote_url) else None
        
        self.session_key = f"{instituicao}_{username}"
        
        if session_path:
            self.session_path = session_path
        else:
            self.session_path = os.path.expanduser(f"~/.activesoft_siga_{self.session_key}.json")
        
        self._load_session_sync()

    def _load_session_sync(self):
        """Helper to run the async session loader for initialization."""
        # Note: This is synchronous initialization for backward compatibility.
        # But remote loading REQUIRES async. If remote is enabled, we'll try to load it
        # on the first request if not already loaded.
        if not self.remote_session_url:
            self._load_session_local()

    async def _ensure_session(self):
         """Ensures session is loaded, especially if remote."""
         if self.remote_session_url and not self.client.cookies.get("auth_jwt"):
             logger.info(f"Checking remote session for {self.session_key}...")
             await self._load_session_remote()

    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self.client.aclose()

    async def _get_csrf_token(self):
        """
        Fetches the login page and extracts the CSRF token.
        """
        logger.info(f"Fetching login page for CSRF token: {self.login_url}")
        response = await self.client.get(self.login_url)
        response.raise_for_status()
        
        match = re.search(r'<input type="hidden" name="csrfmiddlewaretoken" value="([^"]+)"', response.text)
        if match:
            token = match.group(1)
            logger.success(f"CSRF Token found: {token[:15]}...")
            return token
        else:
            raise ValueError("CSRF token not found on the login page.")

    async def login(self):
        """
        Performs the login and initializes the authenticated session.
        """
        # Clear existing cookies to ensure we get the fresh login page instead of a redirect
        self.client.cookies.clear()
        self.csrf_token = await self._get_csrf_token()
        
        payload = {
            "csrfmiddlewaretoken": self.csrf_token,
            "codigo": self.instituicao,
            "login": self.username,
            "senha": self.password
        }
        
        logger.info(f"Attempting to log in as {self.username}...")
        response = await self.client.post(self.login_url, data=payload, headers={"Referer": self.login_url})
        response.raise_for_status()
        
        auth_jwt = self.client.cookies.get("auth_jwt")
        if auth_jwt:
            logger.success("Login successful! Found auth_jwt cookie.")
            
            # Refresh CSRF token from cookies as it often rotates after login
            cookie_csrf = self.client.cookies.get("csrftoken")
            if cookie_csrf:
                self.csrf_token = cookie_csrf
                logger.info("CSRF token updated from cookies.")

            # Set default headers for subsequent API calls
            self.client.headers.update({
                "X-CSRFToken": self.csrf_token,
                "Referer": f"{self.BASE_URL}/alunos/",
                "X-Requested-With": "XMLHttpRequest",
                "Origin": self.BASE_URL
            })
            await self._save_session()
            return True
        else:
            logger.warning("Login might have failed. 'auth_jwt' cookie not found.")
            return False

    def _save_session_sync(self):
        """Triggers session save locally (non-blocking for remote)."""
        self._save_session_local()

    async def _save_session(self):
        """Unified session saver (local + remote)."""
        self._save_session_local()
        if self.remote_session_url:
            await self._save_session_remote()

    def _save_session_local(self):
        """Saves current cookies to the local session file."""
        try:
            cookies_dict = {c.name: c.value for c in self.client.cookies.jar}
            session_data = {
                "cookies": cookies_dict,
                "csrf_token": self.csrf_token
            }
            with open(self.session_path, 'w') as f:
                json.dump(session_data, f)
            logger.debug(f"Local session saved: {self.session_path}")
        except Exception as e:
            logger.error(f"Failed to save local session: {e}")

    async def _save_session_remote(self):
        """Saves current session to the remote API sync service."""
        if not self.remote_session_url:
            return
        
        try:
            cookies_dict = {c.name: c.value for c in self.client.cookies.jar}
            session_data = {
                "cookies": cookies_dict,
                "csrf_token": self.csrf_token
            }
            url = f"{self.remote_session_url}/session/{self.session_key}"
            async with httpx.AsyncClient(timeout=10.0) as temp_client:
                resp = await temp_client.post(url, json=session_data)
                resp.raise_for_status()
            logger.info(f"Remote session synced: {self.session_key}")
        except Exception as e:
            logger.warning(f"Failed to save remote session: {e}")

    def _load_session_local(self):
        """Loads cookies from the local session file."""
        if os.path.exists(self.session_path):
            try:
                with open(self.session_path, 'r') as f:
                    session_data = json.load(f)
                self._apply_session_data(session_data)
                logger.debug(f"Local session loaded: {self.session_path}")
            except Exception as e:
                logger.error(f"Failed to load local session: {e}")

    async def _load_session_remote(self):
        """Loads session from the remote API sync service."""
        if not self.remote_session_url:
            return
        
        try:
            url = f"{self.remote_session_url}/session/{self.session_key}"
            async with httpx.AsyncClient(timeout=10.0) as temp_client:
                resp = await temp_client.get(url)
                resp.raise_for_status()
                session_data = resp.json()
                if session_data:
                    self._apply_session_data(session_data)
                    logger.info(f"Remote session loaded for {self.session_key}")
                else:
                    logger.debug(f"No remote session found for {self.session_key}")
        except Exception as e:
            logger.warning(f"Failed to load remote session: {e}")

    def _apply_session_data(self, session_data: Dict[str, Any]):
        """Helper to apply loaded session data to the client."""
        cookies = session_data.get("cookies", {})
        for name, value in cookies.items():
            self.client.cookies.set(name, value, domain="siga02.activesoft.com.br")
        
        self.csrf_token = session_data.get("csrf_token")
        if self.csrf_token:
             self.client.headers.update({
                "X-CSRFToken": self.csrf_token,
                "Referer": f"{self.BASE_URL}/alunos/",
                "X-Requested-With": "XMLHttpRequest",
                "Origin": self.BASE_URL
            })

    async def _request(self, method: str, url: str, **kwargs):
        """Unified request handler with auto-relogin and remote session awareness."""
        await self._ensure_session()
        try:
            response = await self.client.request(method, url, **kwargs)
            response.raise_for_status()
            return response
        except httpx.HTTPStatusError as e:
            # 401/403 often mean expired session
            if e.response.status_code in (401, 403):
                logger.info("Session expired or unauthorized. Attempting re-login...")
                if await self.login():
                    # After successful login, save session (including remote)
                    await self._save_session()
                    
                    # Retry once after login
                    if "headers" in kwargs:
                        kwargs["headers"].update({
                            "X-CSRFToken": self.csrf_token,
                            "X-Requested-With": "XMLHttpRequest",
                            "Origin": self.BASE_URL
                        })
                    return await self.client.request(method, url, **kwargs)
            raise

    async def list_alunos(self, limit=10, offset=0, search="", pesquisa_qualquer_trecho=True, apenas_ativos=False):
        """
        Retrieves the list of students from the API.
        """
        params = {
            "limit": limit,
            "offset": offset,
            "search": search,
            "pesquisa_qualquer_trecho": str(pesquisa_qualquer_trecho).lower(),
            "apenas_ativos": str(apenas_ativos).lower()
        }
        
        logger.info(f"Listing alunos (limit={limit}, offset={offset})...")
        response = await self._request("GET", self.API_ALUNOS_URL, params=params)
        
        data = response.json()
        logger.success(f"Retrieved {len(data.get('results', []))} students (Total: {data.get('count')})")
        return data

    async def list_turmas(self, limit=10, offset=0, periodo=14):
        """
        Retrieves the list of classes (turmas) from the API.
        """
        params = {
            "limit": limit,
            "offset": offset,
            "periodo": periodo
        }
        
        logger.info(f"Listing turmas (limit={limit}, offset={offset}, periodo={periodo})...")
        response = await self._request("GET", self.API_TURMAS_URL, params=params)
        
        data = response.json()
        logger.success(f"Retrieved {len(data.get('results', []))} turmas (Total: {data.get('count', 'N/A')})")
        return data

    async def list_responsaveis(self, limit=21, offset=0, search="", pesquisa_qualquer_trecho=True, apenas_ativos=True):
        """
        Retrieves the list of responsible parties (guardians) from the API.
        """
        params = {
            "limit": limit,
            "offset": offset,
            "maximoPorPagina": limit,
            "search": search,
            "pesquisa_qualquer_trecho": str(pesquisa_qualquer_trecho).lower(),
            "apenas_ativos": str(apenas_ativos).lower()
        }
        
        logger.info(f"Listing responsaveis (limit={limit}, offset={offset})...")
        response = await self._request("GET", self.API_RESPONSAVEL_URL, params=params)
        
        data = response.json()
        logger.success(f"Retrieved {len(data.get('results', []))} responsible parties (Total: {data.get('count', 'N/A')})")
        return data

    async def get_responsavel_detail(self, responsavel_id):
        """
        Retrieves full details for a specific responsible party.
        """
        url = self.API_RESPONSAVEL_DETALHE_URL_TEMPLATE.format(responsavel_id)
        
        # Update Referer for this specific detail view
        detail_referer = f"{self.BASE_URL}/responsaveis/{responsavel_id}/detalhe"
        headers = {"Referer": detail_referer}
        
        logger.info(f"Fetching details for responsible party ID: {responsavel_id}")
        response = await self._request("GET", url, headers=headers)
        
        data = response.json()
        resp_data = data.get('responsavel', data)
        name = resp_data.get('nome', resp_data.get('nome_completo', 'Unknown'))
        
        logger.success(f"Detail found for: {name}")
        return data

    async def get_aluno_turmas(self, aluno_id, ocultar_turmas_inativas=True):
        """
        Retrieves the classes associated with a specific student.
        """
        params = {
            "aluno": aluno_id,
            "ocultar_turmas_inativas": str(ocultar_turmas_inativas).lower(),
            "limit": 100 # Fetch more to be safe
        }
        
        logger.info(f"Fetching turmas for student ID: {aluno_id}")
        response = await self._request("GET", self.API_ALUNOTURMA_URL, params=params)
        
        data = response.json()
        results = data.get('results', [])
        logger.success(f"Found {len(results)} turmas for student {aluno_id}")
        return results

    async def get_aluno_detail(self, aluno_id):
        """
        Retrieves full details for a specific student.
        """
        url = self.API_DETALHE_URL_TEMPLATE.format(aluno_id)
        
        # Update Referer for this specific detail view
        detail_referer = f"{self.BASE_URL}/alunos/{aluno_id}/detalhe/"
        headers = {"Referer": detail_referer}
        
        logger.info(f"Fetching details for student ID: {aluno_id}")
        response = await self._request("GET", url, headers=headers)
        
        data = response.json()
        student_data = data.get('aluno', data)
        name = student_data.get('nome', student_data.get('nome_completo', 'Unknown'))
        
        logger.success(f"Detail found for: {name}")
        return data

    async def send_message(self, student_id, destinatario_id, message_text, message_title="Aviso", destinatario_tipo="R", origem=2):
        """
        Sends a message through the ActiveSoft API.
        
        Args:
            student_id: The ID of the student.
            destinatario_id: The ID of the recipient (e.g., responsible party ID).
            message_text: The text of the message.
            message_title: The title of the message (default: "Aviso").
            destinatario_tipo: The type of recipient (default: "R").
            origem: The origin code (default: 2).
        """
        payload = [{
            "destinatario_tipo": destinatario_tipo,
            "destinatario_titulo": None,
            "destinatario": destinatario_id,
            "mensagem_texto": message_text,
            "mensagem_tipo": "M", # M for message?
            "mensagem_titulo": message_title,
            "origem": origem,
            "lista_anexos": None,
            "aluno": student_id
        }]
        
        logger.info(f"Sending message to recipient {destinatario_id} for student {student_id}...")
        
        headers = {
            "Content-Type": "application/json;charset=UTF-8",
            "Referer": f"{self.BASE_URL}/enviar_nova_mensagem/",
            "X-CSRFToken": self.csrf_token,
            "X-Requested-With": "XMLHttpRequest",
            "Origin": self.BASE_URL
        }
        
        response = await self._request("POST", self.API_MENSAGEM_SAIDA_URL, json=payload, headers=headers)
        
        data = response.json()
        logger.success(f"Message sent successfully! Response: {data}")
        return data

    async def upload_avatar(self, student_id: int, image_data: str):
        """
        Uploads an avatar (photo) for a student.
        
        Args:
            student_id: The ID of the student.
            image_data: Base64 encoded image string (including data URI prefix).
        """
        url = self.API_AVATAR_UPLOAD_URL_TEMPLATE.format(student_id)
        
        payload = {
            "foto": image_data
        }
        
        headers = {
            "Content-Type": "application/json;charset=UTF-8",
            "Referer": f"{self.BASE_URL}/alunos/{student_id}/detalhe/",
            "X-CSRFToken": self.csrf_token,
            "X-Requested-With": "XMLHttpRequest",
            "Origin": self.BASE_URL
        }
        
        logger.info(f"Uploading avatar for student ID: {student_id}...")
        response = await self._request("PUT", url, json=payload, headers=headers)
        
        if response.status_code in (200, 201, 204):
            logger.success(f"Avatar uploaded successfully for student {student_id}!")
            return True
        else:
            logger.error(f"Failed to upload avatar. Status: {response.status_code}, Body: {response.text}")
            return False

    async def get_bulk_fotos(self, pks: list, tipo: str = "aluno"):
        """
        Retrieves photo URLs for multiple students or responsible parties in bulk.
        
        Args:
            pks: List of IDs.
            tipo: The type of user ("aluno" or "responsavel"). Defaults to "aluno".
        """
        if tipo == "aluno":
            url = self.API_BULK_FOTOS_URL
            params = {
                "pks": ",".join(map(str, pks))
            }
        elif tipo == "responsavel":
            url = self.API_RESPONSAVEL_BULK_FOTOS_URL
            params = [("pks[]", pk) for pk in pks]
        else:
            raise ValueError(f"Invalid tipo: {tipo}. Must be 'aluno' or 'responsavel'.")
        
        logger.info(f"Fetching bulk fotos for {len(pks)} {tipo} IDs...")
        response = await self._request("GET", url, params=params)
        
        data = response.json()
        logger.success(f"Retrieved photo info for {len(data)} {tipo}s.")
        return data
