# app/services/admin_service.py
"""
Admin service for managing users, vendors, and import jobs.

This module provides classes and functions for:
- User management and status control
- Vendor creation with owner user generation
- Vendor verification and activation
- Marketplace import job monitoring
- Platform statistics
"""

import logging
import secrets
import string
from datetime import UTC, datetime

from sqlalchemy import func, or_
from sqlalchemy.orm import Session, joinedload

from app.exceptions import (
    AdminOperationException,
    CannotModifySelfException,
    UserCannotBeDeletedException,
    UserNotFoundException,
    UserRoleChangeException,
    UserStatusChangeException,
    ValidationException,
    VendorAlreadyExistsException,
    VendorNotFoundException,
    VendorVerificationException,
)
from app.exceptions.auth import UserAlreadyExistsException
from middleware.auth import AuthManager
from models.database.company import Company
from models.database.marketplace_import_job import MarketplaceImportJob
from models.database.user import User
from models.database.vendor import Role, Vendor
from models.schema.marketplace_import_job import MarketplaceImportJobResponse
from models.schema.vendor import VendorCreate

logger = logging.getLogger(__name__)


class AdminService:
    """Service class for admin operations following the application's service pattern."""

    # ============================================================================
    # USER MANAGEMENT
    # ============================================================================

    def get_all_users(self, db: Session, skip: int = 0, limit: int = 100) -> list[User]:
        """Get paginated list of all users."""
        try:
            return db.query(User).offset(skip).limit(limit).all()
        except Exception as e:
            logger.error(f"Failed to retrieve users: {str(e)}")
            raise AdminOperationException(
                operation="get_all_users", reason="Database query failed"
            )

    def toggle_user_status(
        self, db: Session, user_id: int, current_admin_id: int
    ) -> tuple[User, str]:
        """Toggle user active status."""
        user = self._get_user_by_id_or_raise(db, user_id)

        # Prevent self-modification
        if user.id == current_admin_id:
            raise CannotModifySelfException(user_id, "deactivate account")

        # Check if user is another admin
        if user.role == "admin" and user.id != current_admin_id:
            raise UserStatusChangeException(
                user_id=user_id,
                current_status="admin",
                attempted_action="toggle status",
                reason="Cannot modify another admin user",
            )

        try:
            original_status = user.is_active
            user.is_active = not user.is_active
            user.updated_at = datetime.now(UTC)
            db.flush()
            db.refresh(user)

            status_action = "activated" if user.is_active else "deactivated"
            message = f"User {user.username} has been {status_action}"

            logger.info(f"{message} by admin {current_admin_id}")
            return user, message

        except Exception as e:
            logger.error(f"Failed to toggle user {user_id} status: {str(e)}")
            raise UserStatusChangeException(
                user_id=user_id,
                current_status="active" if original_status else "inactive",
                attempted_action="toggle status",
                reason="Database update failed",
            )

    def list_users(
        self,
        db: Session,
        page: int = 1,
        per_page: int = 10,
        search: str | None = None,
        role: str | None = None,
        is_active: bool | None = None,
    ) -> tuple[list[User], int, int]:
        """
        Get paginated list of users with filtering.

        Returns:
            Tuple of (users, total_count, total_pages)
        """
        import math

        query = db.query(User)

        # Apply filters
        if search:
            search_term = f"%{search.lower()}%"
            query = query.filter(
                or_(
                    User.username.ilike(search_term),
                    User.email.ilike(search_term),
                    User.first_name.ilike(search_term),
                    User.last_name.ilike(search_term),
                )
            )

        if role:
            query = query.filter(User.role == role)

        if is_active is not None:
            query = query.filter(User.is_active == is_active)

        # Get total count
        total = query.count()
        pages = math.ceil(total / per_page) if total > 0 else 1

        # Apply pagination
        skip = (page - 1) * per_page
        users = (
            query.order_by(User.created_at.desc()).offset(skip).limit(per_page).all()
        )

        return users, total, pages

    def create_user(
        self,
        db: Session,
        email: str,
        username: str,
        password: str,
        first_name: str | None = None,
        last_name: str | None = None,
        role: str = "customer",
        current_admin_id: int | None = None,
    ) -> User:
        """
        Create a new user.

        Raises:
            UserAlreadyExistsException: If email or username already exists
        """
        # Check if email exists
        if db.query(User).filter(User.email == email).first():
            raise UserAlreadyExistsException("Email already registered", field="email")

        # Check if username exists
        if db.query(User).filter(User.username == username).first():
            raise UserAlreadyExistsException("Username already taken", field="username")

        # Create user
        auth_manager = AuthManager()
        user = User(
            email=email,
            username=username,
            hashed_password=auth_manager.hash_password(password),
            first_name=first_name,
            last_name=last_name,
            role=role,
            is_active=True,
        )
        db.add(user)
        db.flush()
        db.refresh(user)

        logger.info(f"Admin {current_admin_id} created user {user.username}")
        return user

    def get_user_details(self, db: Session, user_id: int) -> User:
        """
        Get user with relationships loaded.

        Raises:
            UserNotFoundException: If user not found
        """
        user = (
            db.query(User)
            .options(
                joinedload(User.owned_companies), joinedload(User.vendor_memberships)
            )
            .filter(User.id == user_id)
            .first()
        )

        if not user:
            raise UserNotFoundException(str(user_id))

        return user

    def update_user(
        self,
        db: Session,
        user_id: int,
        current_admin_id: int,
        email: str | None = None,
        username: str | None = None,
        first_name: str | None = None,
        last_name: str | None = None,
        role: str | None = None,
        is_active: bool | None = None,
    ) -> User:
        """
        Update user information.

        Raises:
            UserNotFoundException: If user not found
            UserAlreadyExistsException: If email/username already taken
            UserRoleChangeException: If trying to change own admin role
        """
        user = self._get_user_by_id_or_raise(db, user_id)

        # Prevent changing own admin status
        if user.id == current_admin_id and role and role != "admin":
            raise UserRoleChangeException(
                user_id=user_id,
                current_role=user.role,
                target_role=role,
                reason="Cannot change your own admin role",
            )

        # Check email uniqueness if changing
        if email and email != user.email:
            if db.query(User).filter(User.email == email).first():
                raise UserAlreadyExistsException(
                    "Email already registered", field="email"
                )

        # Check username uniqueness if changing
        if username and username != user.username:
            if db.query(User).filter(User.username == username).first():
                raise UserAlreadyExistsException(
                    "Username already taken", field="username"
                )

        # Update fields
        if email is not None:
            user.email = email
        if username is not None:
            user.username = username
        if first_name is not None:
            user.first_name = first_name
        if last_name is not None:
            user.last_name = last_name
        if role is not None:
            user.role = role
        if is_active is not None:
            user.is_active = is_active

        user.updated_at = datetime.now(UTC)
        db.flush()
        db.refresh(user)

        logger.info(f"Admin {current_admin_id} updated user {user.username}")
        return user

    def delete_user(self, db: Session, user_id: int, current_admin_id: int) -> str:
        """
        Delete a user.

        Raises:
            UserNotFoundException: If user not found
            CannotModifySelfException: If trying to delete yourself
            UserCannotBeDeletedException: If user owns companies
        """
        user = (
            db.query(User)
            .options(joinedload(User.owned_companies))
            .filter(User.id == user_id)
            .first()
        )

        if not user:
            raise UserNotFoundException(str(user_id))

        # Prevent deleting yourself
        if user.id == current_admin_id:
            raise CannotModifySelfException(user_id, "delete account")

        # Prevent deleting users who own companies
        if user.owned_companies:
            raise UserCannotBeDeletedException(
                user_id=user_id,
                reason=f"User owns {len(user.owned_companies)} company(ies). Transfer ownership first.",
                owned_count=len(user.owned_companies),
            )

        username = user.username
        db.delete(user)

        logger.info(f"Admin {current_admin_id} deleted user {username}")
        return f"User {username} deleted successfully"

    def search_users(
        self,
        db: Session,
        query: str,
        limit: int = 10,
    ) -> list[dict]:
        """
        Search users by username or email.

        Used for autocomplete in ownership transfer.
        """
        search_term = f"%{query.lower()}%"
        users = (
            db.query(User)
            .filter(
                or_(User.username.ilike(search_term), User.email.ilike(search_term))
            )
            .limit(limit)
            .all()
        )

        return [
            {
                "id": user.id,
                "username": user.username,
                "email": user.email,
                "is_active": user.is_active,
            }
            for user in users
        ]

    # ============================================================================
    # VENDOR MANAGEMENT
    # ============================================================================

    def create_vendor(self, db: Session, vendor_data: VendorCreate) -> Vendor:
        """
        Create a vendor (storefront/brand) under an existing company.

        The vendor inherits owner and contact information from its parent company.

        Args:
            db: Database session
            vendor_data: Vendor creation data including company_id

        Returns:
            The created Vendor object with company relationship loaded

        Raises:
            ValidationException: If company not found or vendor code/subdomain exists
            AdminOperationException: If creation fails
        """
        try:
            # Validate company exists
            company = (
                db.query(Company).filter(Company.id == vendor_data.company_id).first()
            )
            if not company:
                raise ValidationException(
                    f"Company with ID {vendor_data.company_id} not found"
                )

            # Check if vendor code already exists
            existing_vendor = (
                db.query(Vendor)
                .filter(
                    func.upper(Vendor.vendor_code) == vendor_data.vendor_code.upper()
                )
                .first()
            )
            if existing_vendor:
                raise VendorAlreadyExistsException(vendor_data.vendor_code)

            # Check if subdomain already exists
            existing_subdomain = (
                db.query(Vendor)
                .filter(func.lower(Vendor.subdomain) == vendor_data.subdomain.lower())
                .first()
            )
            if existing_subdomain:
                raise ValidationException(
                    f"Subdomain '{vendor_data.subdomain}' is already taken"
                )

            # Create vendor linked to company
            vendor = Vendor(
                company_id=company.id,
                vendor_code=vendor_data.vendor_code.upper(),
                subdomain=vendor_data.subdomain.lower(),
                name=vendor_data.name,
                description=vendor_data.description,
                letzshop_csv_url_fr=vendor_data.letzshop_csv_url_fr,
                letzshop_csv_url_en=vendor_data.letzshop_csv_url_en,
                letzshop_csv_url_de=vendor_data.letzshop_csv_url_de,
                is_active=True,
                is_verified=False,  # Needs verification by admin
            )
            db.add(vendor)
            db.flush()  # Get vendor.id

            # Create default roles for vendor
            self._create_default_roles(db, vendor.id)

            db.flush()
            db.refresh(vendor)

            logger.info(
                f"Vendor {vendor.vendor_code} created under company {company.name} (ID: {company.id})"
            )

            return vendor

        except (VendorAlreadyExistsException, ValidationException):
            raise
        except Exception as e:
            logger.error(f"Failed to create vendor: {str(e)}")
            raise AdminOperationException(
                operation="create_vendor",
                reason=f"Failed to create vendor: {str(e)}",
            )

    def get_all_vendors(
        self,
        db: Session,
        skip: int = 0,
        limit: int = 100,
        search: str | None = None,
        is_active: bool | None = None,
        is_verified: bool | None = None,
    ) -> tuple[list[Vendor], int]:
        """Get paginated list of all vendors with filtering."""
        try:
            # Eagerly load company relationship to avoid N+1 queries
            query = db.query(Vendor).options(joinedload(Vendor.company))

            # Apply search filter
            if search:
                search_term = f"%{search}%"
                query = query.filter(
                    or_(
                        Vendor.name.ilike(search_term),
                        Vendor.vendor_code.ilike(search_term),
                        Vendor.subdomain.ilike(search_term),
                    )
                )

            # Apply status filters
            if is_active is not None:
                query = query.filter(Vendor.is_active == is_active)
            if is_verified is not None:
                query = query.filter(Vendor.is_verified == is_verified)

            # Get total count (without joinedload for performance)
            count_query = db.query(Vendor)
            if search:
                search_term = f"%{search}%"
                count_query = count_query.filter(
                    or_(
                        Vendor.name.ilike(search_term),
                        Vendor.vendor_code.ilike(search_term),
                        Vendor.subdomain.ilike(search_term),
                    )
                )
            if is_active is not None:
                count_query = count_query.filter(Vendor.is_active == is_active)
            if is_verified is not None:
                count_query = count_query.filter(Vendor.is_verified == is_verified)
            total = count_query.count()

            # Get paginated results
            vendors = query.offset(skip).limit(limit).all()

            return vendors, total
        except Exception as e:
            logger.error(f"Failed to retrieve vendors: {str(e)}")
            raise AdminOperationException(
                operation="get_all_vendors", reason="Database query failed"
            )

    def get_vendor_by_id(self, db: Session, vendor_id: int) -> Vendor:
        """Get vendor by ID."""
        return self._get_vendor_by_id_or_raise(db, vendor_id)

    def verify_vendor(self, db: Session, vendor_id: int) -> tuple[Vendor, str]:
        """Toggle vendor verification status."""
        vendor = self._get_vendor_by_id_or_raise(db, vendor_id)

        try:
            original_status = vendor.is_verified
            vendor.is_verified = not vendor.is_verified
            vendor.updated_at = datetime.now(UTC)

            if vendor.is_verified:
                vendor.verified_at = datetime.now(UTC)

            db.flush()
            db.refresh(vendor)

            status_action = "verified" if vendor.is_verified else "unverified"
            message = f"Vendor {vendor.vendor_code} has been {status_action}"

            logger.info(message)
            return vendor, message

        except Exception as e:
            logger.error(f"Failed to verify vendor {vendor_id}: {str(e)}")
            raise VendorVerificationException(
                vendor_id=vendor_id,
                reason="Database update failed",
                current_verification_status=original_status,
            )

    def toggle_vendor_status(self, db: Session, vendor_id: int) -> tuple[Vendor, str]:
        """Toggle vendor active status."""
        vendor = self._get_vendor_by_id_or_raise(db, vendor_id)

        try:
            original_status = vendor.is_active
            vendor.is_active = not vendor.is_active
            vendor.updated_at = datetime.now(UTC)
            db.flush()
            db.refresh(vendor)

            status_action = "activated" if vendor.is_active else "deactivated"
            message = f"Vendor {vendor.vendor_code} has been {status_action}"

            logger.info(message)
            return vendor, message

        except Exception as e:
            logger.error(f"Failed to toggle vendor {vendor_id} status: {str(e)}")
            raise AdminOperationException(
                operation="toggle_vendor_status",
                reason="Database update failed",
                target_type="vendor",
                target_id=str(vendor_id),
            )

    def delete_vendor(self, db: Session, vendor_id: int) -> str:
        """Delete vendor and all associated data."""
        vendor = self._get_vendor_by_id_or_raise(db, vendor_id)

        try:
            vendor_code = vendor.vendor_code

            # TODO: Delete associated data in correct order
            # - Delete orders
            # - Delete customers
            # - Delete products
            # - Delete team members
            # - Delete roles
            # - Delete import jobs

            db.delete(vendor)

            logger.warning(f"Vendor {vendor_code} and all associated data deleted")
            return f"Vendor {vendor_code} successfully deleted"

        except Exception as e:
            logger.error(f"Failed to delete vendor {vendor_id}: {str(e)}")
            raise AdminOperationException(
                operation="delete_vendor", reason="Database deletion failed"
            )

    def update_vendor(
        self,
        db: Session,
        vendor_id: int,
        vendor_update,  # VendorUpdate schema
    ) -> Vendor:
        """
        Update vendor information (Admin only).

        Can update:
        - Vendor details (name, description, subdomain)
        - Business contact info (contact_email, phone, etc.)
        - Status (is_active, is_verified)

        Cannot update:
        - vendor_code (immutable)
        - company_id (vendor cannot be moved between companies)

        Note: Ownership is managed at the Company level.
        Use company_service.transfer_ownership() for ownership changes.

        Args:
            db: Database session
            vendor_id: ID of vendor to update
            vendor_update: VendorUpdate schema with updated data

        Returns:
            Updated vendor object

        Raises:
            VendorNotFoundException: If vendor not found
            ValidationException: If subdomain already taken
        """
        vendor = self._get_vendor_by_id_or_raise(db, vendor_id)

        try:
            # Get update data
            update_data = vendor_update.model_dump(exclude_unset=True)

            # Handle reset_contact_to_company flag
            if update_data.pop("reset_contact_to_company", False):
                # Reset all contact fields to None (inherit from company)
                update_data["contact_email"] = None
                update_data["contact_phone"] = None
                update_data["website"] = None
                update_data["business_address"] = None
                update_data["tax_number"] = None

            # Convert empty strings to None for contact fields (empty = inherit)
            contact_fields = [
                "contact_email",
                "contact_phone",
                "website",
                "business_address",
                "tax_number",
            ]
            for field in contact_fields:
                if field in update_data and update_data[field] == "":
                    update_data[field] = None

            # Check subdomain uniqueness if changing
            if (
                "subdomain" in update_data
                and update_data["subdomain"] != vendor.subdomain
            ):
                existing = (
                    db.query(Vendor)
                    .filter(
                        Vendor.subdomain == update_data["subdomain"],
                        Vendor.id != vendor_id,
                    )
                    .first()
                )
                if existing:
                    raise ValidationException(
                        f"Subdomain '{update_data['subdomain']}' is already taken"
                    )

            # Update vendor fields
            for field, value in update_data.items():
                setattr(vendor, field, value)

            vendor.updated_at = datetime.now(UTC)

            db.flush()
            db.refresh(vendor)

            logger.info(
                f"Vendor {vendor_id} ({vendor.vendor_code}) updated by admin. "
                f"Fields updated: {', '.join(update_data.keys())}"
            )
            return vendor

        except ValidationException:
            raise
        except Exception as e:
            logger.error(f"Failed to update vendor {vendor_id}: {str(e)}")
            raise AdminOperationException(
                operation="update_vendor", reason=f"Database update failed: {str(e)}"
            )

    # NOTE: Vendor ownership transfer is now handled at the Company level.
    # Use company_service.transfer_ownership() instead.

    # ============================================================================
    # MARKETPLACE IMPORT JOBS
    # ============================================================================

    def get_marketplace_import_jobs(
        self,
        db: Session,
        marketplace: str | None = None,
        vendor_name: str | None = None,
        status: str | None = None,
        skip: int = 0,
        limit: int = 100,
    ) -> list[MarketplaceImportJobResponse]:
        """Get filtered and paginated marketplace import jobs."""
        try:
            query = db.query(MarketplaceImportJob)

            if marketplace:
                query = query.filter(
                    MarketplaceImportJob.marketplace.ilike(f"%{marketplace}%")
                )
            if vendor_name:
                query = query.filter(
                    MarketplaceImportJob.vendor_name.ilike(f"%{vendor_name}%")
                )
            if status:
                query = query.filter(MarketplaceImportJob.status == status)

            jobs = (
                query.order_by(MarketplaceImportJob.created_at.desc())
                .offset(skip)
                .limit(limit)
                .all()
            )

            return [self._convert_job_to_response(job) for job in jobs]

        except Exception as e:
            logger.error(f"Failed to retrieve marketplace import jobs: {str(e)}")
            raise AdminOperationException(
                operation="get_marketplace_import_jobs", reason="Database query failed"
            )

    # ============================================================================
    # STATISTICS
    # ============================================================================

    def get_recent_vendors(self, db: Session, limit: int = 5) -> list[dict]:
        """Get recently created vendors."""
        try:
            vendors = (
                db.query(Vendor).order_by(Vendor.created_at.desc()).limit(limit).all()
            )

            return [
                {
                    "id": v.id,
                    "vendor_code": v.vendor_code,
                    "name": v.name,
                    "subdomain": v.subdomain,
                    "is_active": v.is_active,
                    "is_verified": v.is_verified,
                    "created_at": v.created_at,
                }
                for v in vendors
            ]
        except Exception as e:
            logger.error(f"Failed to get recent vendors: {str(e)}")
            return []

    def get_recent_import_jobs(self, db: Session, limit: int = 10) -> list[dict]:
        """Get recent marketplace import jobs."""
        try:
            jobs = (
                db.query(MarketplaceImportJob)
                .order_by(MarketplaceImportJob.created_at.desc())
                .limit(limit)
                .all()
            )

            return [
                {
                    "id": j.id,
                    "marketplace": j.marketplace,
                    "vendor_name": j.vendor_name,
                    "status": j.status,
                    "total_processed": j.total_processed or 0,
                    "created_at": j.created_at,
                }
                for j in jobs
            ]
        except Exception as e:
            logger.error(f"Failed to get recent import jobs: {str(e)}")
            return []

    # ============================================================================
    # PRIVATE HELPER METHODS
    # ============================================================================

    def _get_user_by_id_or_raise(self, db: Session, user_id: int) -> User:
        """Get user by ID or raise UserNotFoundException."""
        user = db.query(User).filter(User.id == user_id).first()
        if not user:
            raise UserNotFoundException(str(user_id))
        return user

    def _get_vendor_by_id_or_raise(self, db: Session, vendor_id: int) -> Vendor:
        """Get vendor by ID or raise VendorNotFoundException."""
        vendor = (
            db.query(Vendor)
            .options(joinedload(Vendor.company).joinedload(Company.owner))
            .filter(Vendor.id == vendor_id)
            .first()
        )
        if not vendor:
            raise VendorNotFoundException(str(vendor_id), identifier_type="id")
        return vendor

    def _generate_temp_password(self, length: int = 12) -> str:
        """Generate secure temporary password."""
        alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
        return "".join(secrets.choice(alphabet) for _ in range(length))

    def _create_default_roles(self, db: Session, vendor_id: int):
        """Create default roles for a new vendor."""
        default_roles = [
            {"name": "Owner", "permissions": ["*"]},  # Full access
            {
                "name": "Manager",
                "permissions": [
                    "products.*",
                    "orders.*",
                    "customers.view",
                    "inventory.*",
                    "team.view",
                ],
            },
            {
                "name": "Editor",
                "permissions": [
                    "products.view",
                    "products.edit",
                    "orders.view",
                    "inventory.view",
                ],
            },
            {
                "name": "Viewer",
                "permissions": [
                    "products.view",
                    "orders.view",
                    "customers.view",
                    "inventory.view",
                ],
            },
        ]

        for role_data in default_roles:
            role = Role(
                vendor_id=vendor_id,
                name=role_data["name"],
                permissions=role_data["permissions"],
            )
            db.add(role)

    def _convert_job_to_response(
        self, job: MarketplaceImportJob
    ) -> MarketplaceImportJobResponse:
        """Convert database model to response schema."""
        return MarketplaceImportJobResponse(
            job_id=job.id,
            status=job.status,
            marketplace=job.marketplace,
            source_url=job.source_url,
            vendor_id=job.vendor.id if job.vendor else None,
            vendor_code=job.vendor.vendor_code if job.vendor else None,
            vendor_name=job.vendor.name if job.vendor else None,
            imported=job.imported_count or 0,
            updated=job.updated_count or 0,
            total_processed=job.total_processed or 0,
            error_count=job.error_count or 0,
            error_message=job.error_message,
            created_at=job.created_at,
            started_at=job.started_at,
            completed_at=job.completed_at,
        )


# Create service instance
admin_service = AdminService()
