"""
Authentication manager for web dashboard
"""

import hashlib
import secrets
import logging
from datetime import datetime, timedelta
from typing import Optional, Dict, Any, Tuple, List
from flask import Flask, request, session, jsonify
from flask_login import UserMixin
from flask_jwt_extended import create_access_token, decode_token
import jwt

from ..database.manager import DatabaseManager
from ..database.models import UserModel as User, ApiTokenModel as APIToken

logger = logging.getLogger(__name__)


class AuthenticatedUser(UserMixin):
    """User class for Flask-Login"""

    def __init__(self, user_id: int, username: str, email: str, is_admin: bool = False, role: str = 'normal'):
        self.id = user_id
        self.username = username
        self.email = email
        self.is_admin = is_admin
        self.role = role
        # Don't set Flask-Login properties - they are handled by UserMixin

    def get_id(self):
        return str(self.id)

    @property
    def is_authenticated(self):
        """Override UserMixin property"""
        return True

    @property
    def is_active(self):
        """Override UserMixin property"""
        return True

    @property
    def is_anonymous(self):
        """Override UserMixin property"""
        return False

    def to_dict(self) -> Dict[str, Any]:
        return {
            'id': self.id,
            'username': self.username,
            'email': self.email,
            'is_admin': self.is_admin,
            'role': self.role
        }

    def is_admin_user(self) -> bool:
        """Check if user has admin role"""
        return self.role == 'admin' or self.is_admin

    def is_cashier_user(self) -> bool:
        """Check if user has cashier role"""
        return self.role == 'cashier'

    def is_normal_user(self) -> bool:
        """Check if user has normal role"""
        return self.role == 'normal'


class AuthManager:
    """Handles authentication, JWT tokens, and user management"""

    def __init__(self, db_manager: DatabaseManager, app: Flask):
        self.db_manager = db_manager
        self.app = app
        self.jwt_secret = app.config['JWT_SECRET_KEY']
        self.session_timeout = app.config['PERMANENT_SESSION_LIFETIME']

        logger.info("AuthManager initialized")

    def hash_password(self, password: str) -> str:
        """Hash password using SHA-256 with salt"""
        salt = secrets.token_hex(16)
        password_hash = hashlib.sha256((password + salt).encode()).hexdigest()
        return f"{salt}:{password_hash}"

    def verify_password(self, password: str, stored_hash: str) -> bool:
        """Verify password against stored hash"""
        try:
            salt, password_hash = stored_hash.split(':', 1)
            expected_hash = hashlib.sha256((password + salt).encode()).hexdigest()
            return password_hash == expected_hash
        except (ValueError, AttributeError):
            return False

    def authenticate_user(self, username: str, password: str) -> Optional[AuthenticatedUser]:
        """Authenticate user with username/password"""
        try:
            user = self.db_manager.get_user_by_username(username)
            if not user:
                logger.warning(f"Authentication failed: user not found - {username}")
                return None

            if not self.verify_password(password, user.password_hash):
                logger.warning(f"Authentication failed: invalid password - {username}")
                return None

            # Update last login
            user.last_login = datetime.utcnow()
            self.db_manager.save_user(user)

            authenticated_user = AuthenticatedUser(
                user_id=user.id,
                username=user.username,
                email=user.email,
                is_admin=user.is_admin,
                role=getattr(user, 'role', 'normal')  # Default to normal if role field doesn't exist yet
            )

            logger.info(f"User authenticated successfully: {username}")
            return authenticated_user

        except Exception as e:
            logger.error(f"Authentication error: {e}")
            return None

    def create_user(self, username: str, email: str, password: str,
                    is_admin: bool = False, role: str = 'normal') -> Optional[Dict[str, Any]]:
        """Create new user"""
        try:
            # Check if user already exists
            existing_user = self.db_manager.get_user_by_username(username)
            if existing_user:
                logger.warning(f"User creation failed: username already exists - {username}")
                return None

            existing_email = self.db_manager.get_user_by_email(email)
            if existing_email:
                logger.warning(f"User creation failed: email already exists - {email}")
                return None

            # Create new user
            password_hash = self.hash_password(password)
            user = User(
                username=username,
                email=email,
                password_hash=password_hash,
                is_admin=is_admin,
                created_at=datetime.utcnow()
            )

            # Set role (handle backward compatibility)
            if hasattr(user, 'set_role'):
                user.set_role(role)
            elif hasattr(user, 'role'):
                user.role = role
                user.is_admin = (role == 'admin')

            saved_user_data = self.db_manager.save_user(user)
            logger.info(f"User created successfully: {username}")
            return saved_user_data

        except Exception as e:
            logger.error(f"User creation error: {e}")
            return None

    def update_user(self, user_id: int, username: str = None, email: str = None,
                   password: str = None, is_admin: bool = None, role: str = None) -> Optional[Dict[str, Any]]:
        """Update user information"""
        try:
            user = self.db_manager.get_user_by_id(user_id)
            if not user:
                logger.warning(f"User update failed: user not found - {user_id}")
                return None

            # Check for username conflicts if changing username
            if username and username != user.username:
                existing_user = self.db_manager.get_user_by_username(username)
                if existing_user and existing_user.id != user_id:
                    logger.warning(f"User update failed: username already exists - {username}")
                    return None
                user.username = username

            # Check for email conflicts if changing email
            if email and email != user.email:
                existing_email = self.db_manager.get_user_by_email(email)
                if existing_email and existing_email.id != user_id:
                    logger.warning(f"User update failed: email already exists - {email}")
                    return None
                user.email = email

            # Update password if provided
            if password:
                user.password_hash = self.hash_password(password)

            # Update admin status if provided
            if is_admin is not None:
                user.is_admin = is_admin

            # Update role if provided
            if role is not None:
                if hasattr(user, 'set_role'):
                    user.set_role(role)
                elif hasattr(user, 'role'):
                    user.role = role
                    user.is_admin = (role == 'admin')

            # Update timestamp
            user.updated_at = datetime.utcnow()

            # Save user
            updated_user_data = self.db_manager.save_user(user)

            if updated_user_data:
                logger.info(f"User updated successfully: {user.username}")
                return updated_user_data
            else:
                logger.error(f"Failed to save updated user: {user_id}")
                return None

        except Exception as e:
            logger.error(f"User update error: {e}")
            return None

    def change_password(self, user_id: int, old_password: str, new_password: str) -> bool:
        """Change user password"""
        try:
            user = self.db_manager.get_user_by_id(user_id)
            if not user:
                logger.warning(f"Password change failed: user not found - {user_id}")
                return False

            if not self.verify_password(old_password, user.password_hash):
                logger.warning(f"Password change failed: invalid old password - {user_id}")
                return False

            # Update password
            user.password_hash = self.hash_password(new_password)
            user.updated_at = datetime.utcnow()
            self.db_manager.save_user(user)

            logger.info(f"Password changed successfully: {user.username}")
            return True

        except Exception as e:
            logger.error(f"Password change error: {e}")
            return False

    def create_jwt_token(self, user_id: int, expires_hours: int = 24) -> Optional[str]:
        """Create JWT access token"""
        try:
            user = self.db_manager.get_user_by_id(user_id)
            if not user:
                return None

            # Token payload
            payload = {
                'user_id': user.id,
                'username': user.username,
                'is_admin': user.is_admin,
                'exp': datetime.utcnow() + timedelta(hours=expires_hours),
                'iat': datetime.utcnow(),
                'sub': str(user.id)
            }

            # Create token
            token = jwt.encode(payload, self.jwt_secret, algorithm='HS256')

            # Store token in database
            api_token = APIToken(
                user_id=user.id,
                name=f"Web Token {datetime.utcnow().strftime('%Y-%m-%d %H:%M')}",
                token_hash=self._hash_token(token),
                expires_at=datetime.utcnow() + timedelta(hours=expires_hours),
                created_at=datetime.utcnow()
            )

            self.db_manager.save_api_token(api_token)

            logger.info(f"JWT token created for user: {user.username}")
            return token

        except Exception as e:
            logger.error(f"JWT token creation error: {e}")
            return None

    def verify_jwt_token(self, token: str) -> Optional[Dict[str, Any]]:
        """Verify JWT token and return payload"""
        try:
            # Decode token
            payload = jwt.decode(token, self.jwt_secret, algorithms=['HS256'])

            # Check if token is in database and not revoked
            token_hash = self._hash_token(token)
            api_token = self.db_manager.get_api_token_by_hash(token_hash)

            if not api_token or api_token.revoked:
                logger.warning("JWT token verification failed: token revoked or not found")
                return None

            # Check expiration
            if api_token.expires_at < datetime.utcnow():
                logger.warning("JWT token verification failed: token expired")
                return None

            # Update last used
            api_token.last_used = datetime.utcnow()
            self.db_manager.save_api_token(api_token)

            return payload

        except jwt.ExpiredSignatureError:
            logger.warning("JWT token verification failed: signature expired")
            return None
        except jwt.InvalidTokenError as e:
            logger.warning(f"JWT token verification failed: invalid token - {e}")
            return None
        except Exception as e:
            logger.error(f"JWT token verification error: {e}")
            return None

    def revoke_jwt_token(self, token: str) -> bool:
        """Delete JWT token"""
        try:
            token_hash = self._hash_token(token)
            api_token = self.db_manager.get_api_token_by_hash(token_hash)

            if api_token:
                # Delete the token completely from database
                success = self.db_manager.delete_api_token(api_token.id)

                if success:
                    logger.info(f"JWT token deleted: {api_token.name}")
                    return True
                else:
                    logger.error(f"Failed to delete JWT token from database: {api_token.id}")
                    return False

            return False

        except Exception as e:
            logger.error(f"JWT token deletion error: {e}")
            return False

    def create_api_token(self, user_id: int, token_name: str,
                        expires_hours: int = 8760) -> Optional[Tuple[str, Dict[str, Any]]]:  # 1 year default
        """Create long-lived API token"""
        try:
            user = self.db_manager.get_user_by_id(user_id)
            if not user:
                return None

            # Generate secure token
            token = secrets.token_urlsafe(32)
            token_hash = self._hash_token(token)
            expires_at = datetime.utcnow() + timedelta(hours=expires_hours)

            # Create API token record
            api_token = APIToken(
                user_id=user.id,
                name=token_name,
                token_hash=token_hash,
                expires_at=expires_at,
                created_at=datetime.utcnow()
            )

            token_data = self.db_manager.save_api_token(api_token)

            if token_data:
                logger.info(f"API token created: {token_name} for user {user.username}")
                return token, token_data
            else:
                logger.error(f"Failed to save API token to database")
                return None

        except Exception as e:
            logger.error(f"API token creation error: {e}")
            return None

    def verify_api_token(self, token: str) -> Optional[Dict[str, Any]]:
        """Verify API token"""
        try:
            token_hash = self._hash_token(token)
            api_token = self.db_manager.get_api_token_by_hash(token_hash)

            if not api_token or api_token.revoked:
                logger.warning("API token verification failed: token revoked or not found")
                return None

            # Check expiration
            if api_token.expires_at < datetime.utcnow():
                logger.warning("API token verification failed: token expired")
                return None

            # Get user
            user = self.db_manager.get_user_by_id(api_token.user_id)
            if not user:
                logger.warning("API token verification failed: user not found")
                return None

            # Update last used
            api_token.last_used = datetime.utcnow()
            self.db_manager.save_api_token(api_token)

            return {
                'user_id': user.id,
                'username': user.username,
                'is_admin': user.is_admin,
                'role': getattr(user, 'role', 'normal'),
                'token_name': api_token.name,
                'token_id': api_token.id
            }

        except Exception as e:
            logger.error(f"API token verification error: {e}")
            return None

    def list_user_tokens(self, user_id: int) -> list:
        """List all tokens for user"""
        try:
            tokens = self.db_manager.get_user_api_tokens(user_id)
            return [
                {
                    'id': token.id,
                    'name': token.name,
                    'created_at': token.created_at.isoformat(),
                    'expires_at': token.expires_at.isoformat(),
                    'last_used': token.last_used_at.isoformat() if token.last_used_at else None,
                    'revoked': not token.is_active
                }
                for token in tokens
            ]
        except Exception as e:
            logger.error(f"Failed to list user tokens: {e}")
            return []

    def revoke_api_token_by_id(self, token_id: int, user_id: int) -> bool:
        """Delete API token by ID"""
        try:
            api_token = self.db_manager.get_api_token_by_id(token_id)

            if api_token and api_token.user_id == user_id:
                # Delete the token completely from database
                success = self.db_manager.delete_api_token(token_id)

                if success:
                    logger.info(f"API token deleted: {api_token.name}")
                    return True
                else:
                    logger.error(f"Failed to delete API token from database: {token_id}")
                    return False

            return False

        except Exception as e:
            logger.error(f"API token deletion error: {e}")
            return False

    def cleanup_expired_tokens(self):
        """Clean up expired tokens"""
        try:
            count = self.db_manager.cleanup_expired_tokens()
            if count > 0:
                logger.info(f"Cleaned up {count} expired tokens")
        except Exception as e:
            logger.error(f"Token cleanup error: {e}")

    def _hash_token(self, token: str) -> str:
        """Hash token for secure storage"""
        return hashlib.sha256(token.encode()).hexdigest()

    def require_auth(self, f=None):
        """Decorator for routes requiring authentication"""
        from functools import wraps

        def decorator(func):
            @wraps(func)
            def decorated_function(*args, **kwargs):
                print(f"AUTH_DECORATOR: Called for {request.path}")
                auth_header = request.headers.get('Authorization')
                print(f"AUTH_DECORATOR: Auth header: {auth_header}")

                # Check if request is from localhost or 127.0.0.1 - auto-authenticate as admin
                if request.remote_addr in ['127.0.0.1', 'localhost']:
                    print("AUTH_DECORATOR: Localhost request detected - auto-authenticating as admin")
                    request.current_user = {
                        'user_id': 0,
                        'username': 'localhost_admin',
                        'is_admin': True,
                        'role': 'admin'
                    }
                    return func(*args, **kwargs)

                if auth_header and auth_header.startswith('Bearer '):
                    token = auth_header.split(' ', 1)[1]
                    print(f"AUTH_DECORATOR: Token received: {token[:20]}...")

                    # Try JWT token first
                    payload = self.verify_jwt_token(token)
                    if payload:
                        print(f"AUTH_DECORATOR: JWT token verified for user: {payload.get('username')}")
                        request.current_user = payload
                        return func(*args, **kwargs)
                    else:
                        print("AUTH_DECORATOR: JWT token verification failed")

                    # Try API token
                    api_data = self.verify_api_token(token)
                    if api_data:
                        print(f"AUTH_DECORATOR: API token verified for user: {api_data.get('username')}")
                        request.current_user = api_data
                        return func(*args, **kwargs)
                    else:
                        print("AUTH_DECORATOR: API token verification failed")
                else:
                    print("AUTH_DECORATOR: No Bearer token in Authorization header")

                print("AUTH_DECORATOR: Authentication failed, returning 401")
                return jsonify({'error': 'Authentication required'}), 401

            return decorated_function

        # If called without arguments, return the decorator
        if f is None:
            return decorator
        # If called with a function, apply the decorator immediately
        else:
            return decorator(f)


def require_api_auth(f=None):
    """Standalone API auth decorator that uses g.auth_manager"""
    from functools import wraps
    from flask import g

    def decorator(func):
        @wraps(func)
        def decorated_function(*args, **kwargs):
            print(f"API_AUTH_DECORATOR: Called for {request.path}")
            auth_header = request.headers.get('Authorization')
            print(f"API_AUTH_DECORATOR: Auth header: {auth_header}")

            # Get auth_manager from Flask g context
            auth_manager = g.get('auth_manager')
            if not auth_manager:
                print("API_AUTH_DECORATOR: No auth_manager in g context, falling back to 401")
                return jsonify({'error': 'Authentication system not available'}), 401

            # Check if request is from localhost or 127.0.0.1 - auto-authenticate as admin
            if request.remote_addr in ['127.0.0.1', 'localhost']:
                print("API_AUTH_DECORATOR: Localhost request detected - auto-authenticating as admin")
                request.current_user = {
                    'user_id': 0,
                    'username': 'localhost_admin',
                    'is_admin': True,
                    'role': 'admin'
                }
                return func(*args, **kwargs)

            if auth_header and auth_header.startswith('Bearer '):
                token = auth_header.split(' ', 1)[1]
                print(f"API_AUTH_DECORATOR: Token received: {token[:20]}...")

                # Try JWT token first
                payload = auth_manager.verify_jwt_token(token)
                if payload:
                    print(f"API_AUTH_DECORATOR: JWT token verified for user: {payload.get('username')}")
                    request.current_user = payload
                    return func(*args, **kwargs)
                else:
                    print("API_AUTH_DECORATOR: JWT token verification failed")

                # Try API token
                api_data = auth_manager.verify_api_token(token)
                if api_data:
                    print(f"API_AUTH_DECORATOR: API token verified for user: {api_data.get('username')}")
                    request.current_user = api_data
                    return func(*args, **kwargs)
                else:
                    print("API_AUTH_DECORATOR: API token verification failed")
            else:
                print("API_AUTH_DECORATOR: No Bearer token in Authorization header")

            print("API_AUTH_DECORATOR: Authentication failed, returning 401")
            return jsonify({'error': 'Authentication required'}), 401

        return decorated_function

    # If called without arguments, return the decorator
    if f is None:
        return decorator
    # If called with a function, apply the decorator immediately
    else:
        return decorator(f)

    def require_admin(self, f=None):
        """Decorator for routes requiring admin access"""
        from functools import wraps

        def decorator(func):
            @wraps(func)
            def decorated_function(*args, **kwargs):
                if not hasattr(request, 'current_user'):
                    return jsonify({'error': 'Authentication required'}), 401

                user_role = request.current_user.get('role', 'normal')
                is_admin = request.current_user.get('is_admin', False)

                if user_role != 'admin' and not is_admin:
                    return jsonify({'error': 'Admin access required'}), 403

                return func(*args, **kwargs)

            return decorated_function

        # If called without arguments, return the decorator
        if f is None:
            return decorator
        # If called with a function, apply the decorator immediately
        else:
            return decorator(f)

    def require_role(self, allowed_roles: List[str]):
        """Decorator for routes requiring specific roles"""
        from functools import wraps

        def decorator(f):
            @wraps(f)
            def decorated_function(*args, **kwargs):
                if not hasattr(request, 'current_user'):
                    return jsonify({'error': 'Authentication required'}), 401

                user_role = request.current_user.get('role', 'normal')

                if user_role not in allowed_roles:
                    return jsonify({'error': f'Access denied. Required roles: {", ".join(allowed_roles)}'}), 403

                return f(*args, **kwargs)

            return decorated_function
        return decorator

    def get_auth_decorator(self, require_admin=False):
        """Get the appropriate auth decorator based on availability"""
        if hasattr(self, '_app') and self._app:
            if require_admin:
                return self.require_admin()
            else:
                return self.require_auth()
        else:
            # Fallback to Flask-Login's login_required
            from flask_login import login_required
            return login_required