"""
Authentication Routes
Based on login.py authentication logic
"""
import secrets
import os
import time
import hashlib
from datetime import datetime, timedelta
from fastapi import APIRouter, HTTPException, Depends
from sqlalchemy.orm import Session
from sqlalchemy.exc import OperationalError
from pydantic import BaseModel, EmailStr
from typing import Optional, List, Any, Dict, Union

from app.core.database import get_db, SessionLocal
from app.models.user import User
from app.models.agent import AccessManage, AgentManagement, AgentAdmin
from app.models.admin import AdminConfiguration


# Schemas
class LoginRequest(BaseModel):
    email: str
    password: str
    client_id: Optional[str] = None
    force_login: Optional[bool] = False



class LoginByUserIdRequest(BaseModel):
    user_id: int
    agent_id: Optional[Union[str, List[str]]] = None
    client_id: Optional[str] = None

class AgentInfo(BaseModel):
    agent_id: str
    agent_name: str
    endpoint: str
    data_source_type: Optional[str] = None
    db_type: Optional[str] = None
    
    class Config:
        from_attributes = True


class LoginResponse(BaseModel):
    success: bool
    message: str
    user_id: Optional[int] = None
    name: Optional[str] = None
    email: Optional[str] = None
    token: Optional[str] = None
    agents: Optional[List[AgentInfo]] = None


class TokenVerifyRequest(BaseModel):
    token: str


class UserAccessCheck(BaseModel):
    user_id: str
    agent_id: str


class UserAccessResponse(BaseModel):
    has_access: bool
    message: str


router = APIRouter(prefix="/auth", tags=["Authentication"])


def load_initial_config() -> Dict[str, str]:
    """Read initial_config.txt (key = value per line) and return dict of defaults."""
    cfg = {}
    try:
        # Try to find config.txt in the project root
        cfg_path = os.path.join("initial_config.txt")
        if not os.path.exists(cfg_path):
            # Try alternative paths
            cfg_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "initial_config.txt")
        
        if os.path.exists(cfg_path):
            with open(cfg_path, "r", encoding="utf-8") as f:
                for line in f:
                    line = line.strip()
                    if not line or line.startswith("#"):
                        continue
                    if "=" in line:
                        k, v = line.split("=", 1)
                        v = v.strip().rstrip(";").strip()
                        cfg[k.strip()] = v
    except Exception as e:
        print(f"load_initial_config error: {e}")
    return cfg


def create_organisation_from_config(db: Session) -> bool:
    """
    Create/update organisation details from initial_config.txt based on DB_TYPE in config.
    - If db_type = "Oracle": host, port, db_name will be empty, rest values auto-filled from initial_config.txt
    - If db_type = "SQL": connection_string will be empty, rest values auto-filled from initial_config.txt
    - If db_type = "MSSQL": SERVER, port, db_name filled; connection_string and host empty (SERVER stored under SERVER key)
    """
    try:
        # Load config from initial_config.txt
        initial_config = load_initial_config()
        
        if not initial_config:
            return False  # Config file not found or empty
        
        # Get DB type from config (normalize to match expected values)
        config_db_type = initial_config.get("DB_TYPE", "").strip().lower()
        if config_db_type == "oracle":
            db_type = "oracle"
        elif config_db_type == "mssql":
            db_type = "mssql"
        else:
            db_type = "sql"
        
        # Create timestamp in the same format as save_local_db.py
        current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        
        # Prepare organisation data from initial_config.txt
        gemini_from_file = (
            initial_config.get("gemini_api_key", "")
            or initial_config.get("GEMINI_API_KEY", "")
            or initial_config.get("GOOGLE_API_KEY", "")
        )
        claude_from_file = (
            initial_config.get("claude_api_key", "")
            or initial_config.get("CLAUDE_API_KEY", "")
        )
        org_data = {
            "org_id": initial_config.get("ORG_ID", ""),
            "org_name": initial_config.get("ORG_NAME", ""),
            "open_api_key": initial_config.get("OPEN_API_KEY", ""),
            "gemini_api_key": gemini_from_file,
            "claude_api_key": claude_from_file,
            "web_key": initial_config.get("WEB_KEY", ""),
            "base_url": initial_config.get("BASE_URL", ""),
            "scheme": initial_config.get("SCHEME", ""),
            "lib_path": initial_config.get("LIB_PATH", ""),
            "smtp_server": initial_config.get("SMTP_SERVER", ""),
            "smtp_port": initial_config.get("SMTP_PORT", ""),
            "smtp_username": initial_config.get("SMTP_USERNAME", ""),
            "smtp_password": initial_config.get("SMTP_PASSWORD", ""),
            "sender_email_id": initial_config.get("SENDER_EMAIL_ID", ""),
            "db_type": db_type,
            "db_username": initial_config.get("DB_USERNAME", ""),
            "db_password": initial_config.get("DB_PASSWORD", ""),
            "tesseract_path": initial_config.get("TESSERACT_PATH", ""),
        }

        
        # Add db_type specific fields
        if db_type == "sql":
            # For SQL: host, port, db_name are filled, connection_string is empty
            org_data["host"] = initial_config.get("HOST", "")
            org_data["port"] = initial_config.get("PORT", "")
            org_data["db_name"] = initial_config.get("DB_NAME", "")
            org_data["connection_string"] = ""  # Empty for SQL
        elif db_type == "mssql":
            org_data["server"] = initial_config.get("SERVER", "")
            org_data["port"] = initial_config.get("PORT", "")
            org_data["db_name"] = initial_config.get("DB_NAME", "")
            org_data["connection_string"] = ""
            org_data["host"] = ""
        elif db_type == "oracle":
            # For Oracle: connection_string and lib_path are filled, host/port/db_name are empty
            org_data["connection_string"] = initial_config.get("CONNECTION_STRING", "")
            org_data["host"] = ""  # Empty for Oracle
            org_data["port"] = ""  # Empty for Oracle
            org_data["db_name"] = ""  # Empty for Oracle
        
        # Store all configurations in admin_configuration table
        for key, value in org_data.items():
            upper_value = value.strip() if value else ""
            upper_key = key.upper()
            
            # Check if configuration already exists
            existing_config = db.query(AdminConfiguration).filter(
                AdminConfiguration.configuration_name == upper_key,
                AdminConfiguration.type == "C"
            ).first()
            
            if existing_config:
                print("Already exists")
                pass
            else:
                # Create new configuration
                config = AdminConfiguration(
                    configuration_name=upper_key,
                    type="C",  # "C" for core/organisation configs
                    value=upper_value,
                    created_at=current_time,
                    updated_at=current_time
                )
                db.add(config)
                print("New configuration created")
        
        db.commit()
        return True
    except Exception as e:
        print(f"Error creating organisation from config: {e}")
        db.rollback()
        return False


def get_user_agents(user_id: str, db: Session) -> List[dict]:
    """Get all agents associated with a user from access_manage and agent_management tables"""
    agents = []
    
    # Special case: user_id == "1" gets all agents with is_agent_admin = True
    if user_id == "1":
        all_agents = db.query(AgentManagement).all()
        if(all_agents == []):
            agents.append({
                "agent_id": "0",
                "agent_name": "Main Agent",
                "endpoint": "",
                "data_source_type": "Main",
                "default_data_source_type": "Database",
                "db_type": "Oracle",
                "database": "Oracle",
                "api": "",
                "file": "",
                "is_agent_admin": True,  # User 1 is admin for all agents
                "access_start_date": "2026-01-01",
                "access_end_date": "2026-01-01"
            })
        for agent in all_agents:
            agents.append({
                "agent_id": agent.agent_id,
                "agent_name": agent.agent_name,
                "endpoint": agent.endpoint,
                "data_source_type": agent.data_source_type,
                "default_data_source_type": agent.default_data_source_type,
                "db_type": agent.db_type,
                "database": agent.database,
                "api": agent.api,
                "file": agent.file,
                "is_agent_admin": True,  # User 1 is admin for all agents
                "access_start_date": "",
                "access_end_date": ""
            })
        return agents
    
    # For other users, get agents from access_manage table
    access_list = db.query(AccessManage).filter(AccessManage.user_id == user_id).all()
    
    for access in access_list:
        # Check if access is still valid
        try:
            end_date = datetime.fromisoformat(access.end_date)
            if end_date < datetime.now():
                continue  # Skip expired access
        except:
            pass
        
        # Get agent details from agent_management
        agent = db.query(AgentManagement).filter(
            AgentManagement.agent_id == access.agent_id
        ).first()
            
        if agent:
            # Check if user is admin for this agent
            agent_admin_record = db.query(AgentAdmin).filter(
                AgentAdmin.agent_id == agent.agent_id,
                AgentAdmin.admin_id == user_id
            ).first()
            is_agent_admin = agent_admin_record is not None
            
            agents.append({
                "agent_id": agent.agent_id,
                "agent_name": agent.agent_name,
                "endpoint": agent.endpoint,
                "data_source_type": agent.data_source_type,
                "default_data_source_type": agent.default_data_source_type,
                "db_type": agent.db_type,
                "database": agent.database,
                "api": agent.api,
                "file": agent.file,
                "is_agent_admin": is_agent_admin,
                "access_start_date": access.start_date,
                "access_end_date": access.end_date,
                "license_name": access.license_number,
                "version": "M5:1.0.0"
            })
    return agents


@router.post("/login")
def login(credentials: LoginRequest, db: Session = Depends(get_db)):
    """
    Verify user credentials and return login response with associated agents
    Based on verify_login_credentials from login.py
    """
    email = credentials.email.lower()
    password = credentials.password
   
    # Query user from database
    user = db.query(User).filter(User.email == email).first()
    if user is None:
        return {
            "success": False,
            "message": "Invalid Credentials! Please try again."
        }
    
    if user.password != password or user.email != email:
         return {
            "success": False,
            "message": "Invalid Credentials! Please try again."
        }
    
    # Check user status
    if user.status.lower() != "active":
        return {
            "success": False,
            "message": "User account is not active. Please contact administrator."
        }
    
    if user.id == 1:
        create_organisation_from_config(db)
    
    # Check if user already has a token and force_login is False
    already_logged_in = False
    if user.user_token and user.user_token.strip() and not credentials.force_login:
        already_logged_in = True
        new_token = user.user_token
        return {
            "success": True,    
            "message": "Already logged in! Do you to logout from previous session?",
            "details": {
                "user_id": user.id,
                "name": user.name,
                "email": user.email,
                "is_admin": True if user.id == 1 else False,
                "already_logged_in": already_logged_in,
                "agents": []
            }
        }
    else:
        # Create new token using MD5(client_id + email + currentdatetime)
        client_id = credentials.client_id or ""
        current_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        token_string = f"{client_id}{user.email}{current_datetime}"
        new_token = hashlib.md5(token_string.encode()).hexdigest()
        
        # Update user token in database
        user.user_token = new_token
        user.updated_at = datetime.now().isoformat()
        db.commit()
    
    print(f"User ID: {user.id}")
    # Get user's associated agents
    user_agents = get_user_agents(str(user.id), db)
    if(user_agents == []):
        return {
            "success": False,
            "message": "No agents found for this user. Please contact administrator.",
            "details": {
                "user_id": user.id,
                "name": user.name,
                "email": user.email,
                "token": new_token,
                "is_admin": True if user.id == 1 else False,
                "already_logged_in": already_logged_in,
                "agents": user_agents
            }
        }
    if already_logged_in:
        return {
            "success": True,
            "message": "Login Successfully!",
            "details": {
                "user_id": user.id,
                "name": user.name,
                "email": user.email,
                "token": new_token,
                "is_admin": True if user.id == 1 else False,
                "already_logged_in": already_logged_in,
                "agents": user_agents
            }
        }
    return {
        "success": True,
        "message": "Login Successfully!",
        "details": {
            "user_id": user.id,
            "name": user.name,
            "email": user.email,
            "token": new_token,
            "is_admin": True if user.id == 1 else False,
            "already_logged_in": already_logged_in,
            "agents": user_agents
        }
    }


@router.post("/login-by-userid")
def login_by_user_id(data: LoginByUserIdRequest, db: Session = Depends(get_db)):
    """
    Get user details and associated agents by user_id
    Generates new token, updates database, and verifies agent access if agent_id provided
    """
    # Query user from database
    user = db.query(User).filter(User.id == data.user_id).first()
    
    if not user:
        return {
            "success": False,
            "message": "User not found",
            "details": {}
        }
    
    # Check user status
    if user.status.lower() != "active":
        return {
            "success": False,
            "message": "User account is not active. Please contact administrator.",
            "details": {}
        }
    
    # Generate new token using MD5(client_id + email + currentdatetime)
    client_id = data.client_id or ""
    current_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    token_string = f"{client_id}{user.email}{current_datetime}"
    new_token = hashlib.md5(token_string.encode()).hexdigest()
    
    # Update user token in database
    user.user_token = new_token
    user.updated_at = datetime.now().isoformat()
    db.commit()
    
    # Get user's associated agents
    user_agents = get_user_agents(str(user.id), db)

    # Normalize agent_id to list: single string -> [s], list -> as-is, None/empty -> no filter
    requested_ids = None
    if data.agent_id is not None:
        if isinstance(data.agent_id, list):
            requested_ids = [str(a) for a in data.agent_id if a is not None and str(a).strip()]
        else:
            requested_ids = [str(data.agent_id)] if str(data.agent_id).strip() else []
        # Deduplicate while preserving order
        seen = set()
        requested_ids = [a for a in requested_ids if a not in seen and not seen.add(a)]

    base_details = {
        "user_id": user.id,
        "name": user.name,
        "email": user.email,
        "token": new_token,
        "is_admin": True if user.id == 1 else False,
        "agents": []
    }

    if requested_ids is not None and len(requested_ids) > 0:
        # Filter to requested agents: find each id in user_agents
        user_agents_by_id = {str(agent.get("agent_id")): agent for agent in user_agents}
        matched_agents = []
        no_access_ids = []
        not_found_ids = []
        for aid in requested_ids:
            agent = user_agents_by_id.get(str(aid))
            if agent is not None:
                matched_agents.append(agent)
            else:
                agent_exists = db.query(AgentManagement).filter(
                    AgentManagement.agent_id == aid
                ).first()
                if agent_exists:
                    no_access_ids.append(aid)
                else:
                    not_found_ids.append(aid)

        if no_access_ids:
            msg = f"User does not have access to agent(s) '{', '.join(no_access_ids)}'. Please contact administrator."
            return {"success": False, "message": msg, "details": base_details}
        if not_found_ids:
            msg = f"Agent(s) '{', '.join(not_found_ids)}' not found in the system."
            return {"success": False, "message": msg, "details": base_details}

        # When multiple agents are requested, all must have the same default_data_source_type (case insensitive)
        if len(matched_agents) > 1:
            sources = {str(a.get("default_data_source_type") or "").strip().lower() for a in matched_agents}
            if len(sources) > 1:
                msg = "All selected agents must have the same default data source type. Please select agents with a matching data source."
                return {"success": False, "message": msg, "details": base_details}

        user_agents = matched_agents
        n = len(user_agents)
        message = f"Login successful! Access granted to {n} agent(s)." if n != 1 else f"Login successful! Access granted to agent '{user_agents[0].get('agent_name')}'."
    else:
        # No specific agent(s) requested, return all agents
        if not user_agents:
            return {
                "success": False,
                "message": "No agents found for this user. Please contact administrator.",
                "details": base_details
            }
        message = "Login successful!"
    
    return {
        "success": True,
        "message": message,
        "details": {
            "user_id": user.id,
            "name": user.name,
            "email": user.email,
            "token": new_token,
            "is_admin": True if user.id == 1 else False,
            "agents": user_agents
        }
    }

@router.post("/logout")
def logout(data: TokenVerifyRequest, db: Session = Depends(get_db)):
    """Logout user by invalidating token"""
    user = db.query(User).filter(User.user_token == data.token).first()
    
    if user:
        user.user_token = ""
        user.updated_at = datetime.now().isoformat()
        db.commit()
    
    return {
        "success": True,
        "message": "Logged out successfully",
        "details": {}
    }
