package de.cannamanage.service;

import de.cannamanage.domain.entity.Club;
import de.cannamanage.domain.entity.InviteToken;
import de.cannamanage.domain.entity.StaffAccount;
import de.cannamanage.domain.entity.User;
import de.cannamanage.domain.enums.StaffPermission;
import de.cannamanage.domain.enums.UserRole;
import de.cannamanage.service.repository.ClubRepository;
import de.cannamanage.service.repository.InviteTokenRepository;
import de.cannamanage.service.repository.StaffAccountRepository;
import de.cannamanage.service.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;

import java.security.SecureRandom;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Pattern;

import static org.springframework.http.HttpStatus.*;

/**
 * Staff management service — CRUD operations + invite flow.
 * Handles: staff creation (with invite email), permission updates (with token revocation),
 * and deactivation.
 */
@Slf4j
@Service
public class StaffService {

    private static final int TOKEN_BYTES = 32;
    private static final long INVITE_EXPIRY_HOURS = 72;
    private static final SecureRandom SECURE_RANDOM = new SecureRandom();

    private final UserRepository userRepository;
    private final StaffAccountRepository staffAccountRepository;
    private final InviteTokenRepository inviteTokenRepository;
    private final ClubRepository clubRepository;
    private final EmailService emailService;
    private final TokenRevocationService tokenRevocationService;

    public StaffService(UserRepository userRepository,
                        StaffAccountRepository staffAccountRepository,
                        InviteTokenRepository inviteTokenRepository,
                        ClubRepository clubRepository,
                        EmailService emailService,
                        TokenRevocationService tokenRevocationService) {
        this.userRepository = userRepository;
        this.staffAccountRepository = staffAccountRepository;
        this.inviteTokenRepository = inviteTokenRepository;
        this.clubRepository = clubRepository;
        this.emailService = emailService;
        this.tokenRevocationService = tokenRevocationService;
    }

    @Transactional(readOnly = true)
    public List<StaffAccount> listStaff(UUID tenantId) {
        return staffAccountRepository.findByTenantIdAndActiveTrue(tenantId);
    }

    @Transactional(readOnly = true)
    public StaffAccount getStaff(UUID tenantId, UUID staffId) {
        StaffAccount staff = staffAccountRepository.findById(staffId)
                .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Staff account not found"));
        if (!staff.getTenantId().equals(tenantId)) {
            throw new ResponseStatusException(NOT_FOUND, "Staff account not found");
        }
        return staff;
    }

    /**
     * Creates a new staff member: User (inactive) + StaffAccount + InviteToken + sends email.
     * Validates email against club's allowedEmailPattern if configured.
     */
    @Transactional
    public StaffAccount createStaff(UUID tenantId, String email, String displayName,
                                    Set<StaffPermission> permissions, String templateName) {
        // Resolve permissions from template if provided
        Set<StaffPermission> resolvedPermissions = permissions;
        if (templateName != null && !templateName.isBlank()) {
            resolvedPermissions = StaffTemplates.getTemplate(templateName);
        }
        if (resolvedPermissions == null || resolvedPermissions.isEmpty()) {
            throw new ResponseStatusException(BAD_REQUEST, "Permissions must not be empty");
        }

        // Validate email uniqueness within tenant
        if (userRepository.existsByEmailAndTenantId(email, tenantId)) {
            throw new ResponseStatusException(CONFLICT, "Email already in use for this club");
        }

        // Validate email against club's allowed pattern
        Club club = clubRepository.findById(tenantId)
                .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Club not found"));
        validateEmailPattern(email, club.getAllowedEmailPattern());

        // Create User (inactive, no password)
        User user = new User();
        user.setTenantId(tenantId);
        user.setEmail(email);
        user.setPasswordHash(""); // No password until invite is accepted
        user.setRole(UserRole.ROLE_STAFF);
        user.setActive(false);
        user = userRepository.save(user);

        // Create StaffAccount
        StaffAccount staffAccount = new StaffAccount();
        staffAccount.setTenantId(tenantId);
        staffAccount.setUserId(user.getId());
        staffAccount.setDisplayName(displayName);
        staffAccount.setGrantedPermissions(resolvedPermissions);
        staffAccount.setActive(true);
        staffAccount.setInvitedAt(Instant.now());
        staffAccount = staffAccountRepository.save(staffAccount);

        // Create InviteToken (72h expiry, SecureRandom 32-byte Base64 token)
        String tokenValue = generateSecureToken();
        InviteToken inviteToken = new InviteToken();
        inviteToken.setUser(user);
        inviteToken.setToken(tokenValue);
        inviteToken.setExpiresAt(Instant.now().plus(INVITE_EXPIRY_HOURS, ChronoUnit.HOURS));
        inviteTokenRepository.save(inviteToken);

        // Send invite email (token value is NOT logged per security review)
        emailService.sendInviteEmail(email, displayName, club.getName(), tokenValue);

        log.info("Staff member created: {} for tenant {}", email, tenantId);
        return staffAccount;
    }

    /**
     * Updates staff permissions and/or display name.
     * Permission changes trigger token revocation for the affected user.
     */
    @Transactional
    public StaffAccount updateStaff(UUID tenantId, UUID staffId, String displayName,
                                    Set<StaffPermission> permissions, String templateName, Boolean active) {
        StaffAccount staff = getStaff(tenantId, staffId);

        boolean permissionsChanged = false;

        if (displayName != null && !displayName.isBlank()) {
            staff.setDisplayName(displayName);
        }

        // Resolve permissions from template if provided
        Set<StaffPermission> newPermissions = permissions;
        if (templateName != null && !templateName.isBlank()) {
            newPermissions = StaffTemplates.getTemplate(templateName);
        }

        if (newPermissions != null && !newPermissions.equals(staff.getGrantedPermissions())) {
            staff.setGrantedPermissions(newPermissions);
            permissionsChanged = true;
        }

        if (active != null) {
            staff.setActive(active);
            if (!active) {
                permissionsChanged = true; // Deactivation also requires token revocation
            }
        }

        staff = staffAccountRepository.save(staff);

        // Revoke all tokens on permission change (security requirement)
        if (permissionsChanged) {
            tokenRevocationService.revokeAllForUser(staff.getUserId());
            log.info("Tokens revoked for staff {} due to permission/status change", staff.getUserId());
        }

        return staff;
    }

    /**
     * Deactivates a staff member — sets inactive and revokes all JWT tokens.
     */
    @Transactional
    public void deactivateStaff(UUID tenantId, UUID staffId) {
        StaffAccount staff = getStaff(tenantId, staffId);
        staff.setActive(false);
        staffAccountRepository.save(staff);

        // Also deactivate the user account
        User user = userRepository.findById(staff.getUserId())
                .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "User not found"));
        user.setActive(false);
        userRepository.save(user);

        // Revoke all tokens
        tokenRevocationService.revokeAllForUser(staff.getUserId());
        log.info("Staff {} deactivated for tenant {}", staffId, tenantId);
    }

    /**
     * Validates email against club's allowedEmailPattern (regex).
     * If no pattern is configured, all emails are accepted.
     */
    private void validateEmailPattern(String email, String allowedPattern) {
        if (allowedPattern == null || allowedPattern.isBlank()) {
            return; // No restriction
        }
        try {
            Pattern pattern = Pattern.compile(allowedPattern, Pattern.CASE_INSENSITIVE);
            if (!pattern.matcher(email).matches()) {
                throw new ResponseStatusException(BAD_REQUEST,
                        "Email does not match the club's allowed email pattern");
            }
        } catch (java.util.regex.PatternSyntaxException e) {
            log.warn("Invalid email pattern configured for club: {}", allowedPattern);
            // Don't block staff creation due to misconfigured pattern
        }
    }

    /**
     * Generates a cryptographically secure token: 32 bytes → Base64 URL-safe encoding.
     */
    private String generateSecureToken() {
        byte[] bytes = new byte[TOKEN_BYTES];
        SECURE_RANDOM.nextBytes(bytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
    }
}
