package de.cannamanage.api.security;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.crypto.SecretKey;
import java.time.Instant;
import java.util.*;
import java.util.function.Function;

/**
 * JWT token generation and validation service.
 * Access tokens: 1 hour expiry, includes jti + permissions for STAFF.
 * Refresh tokens: 30 days expiry.
 */
@Service
public class JwtService {

    @Value("${cannamanage.security.jwt.secret}")
    private String secretKey;

    @Value("${cannamanage.security.jwt.access-token-expiry:3600}")
    private long accessTokenExpiry; // seconds

    @Value("${cannamanage.security.jwt.refresh-token-expiry:2592000}")
    private long refreshTokenExpiry; // seconds (30 days)

    /**
     * Generate access token for ADMIN/MEMBER roles (no permissions claim needed).
     */
    public String generateAccessToken(UUID userId, UUID tenantId, String role, String email) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("tenant_id", tenantId.toString());
        claims.put("role", role);
        claims.put("email", email);
        claims.put("jti", UUID.randomUUID().toString());

        return buildToken(claims, userId.toString(), accessTokenExpiry);
    }

    /**
     * Generate access token for STAFF role — includes permissions list.
     */
    public String generateStaffAccessToken(UUID userId, UUID tenantId, String email, List<String> permissions) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("tenant_id", tenantId.toString());
        claims.put("role", "STAFF");
        claims.put("email", email);
        claims.put("jti", UUID.randomUUID().toString());
        claims.put("permissions", permissions);

        return buildToken(claims, userId.toString(), accessTokenExpiry);
    }

    public String generateRefreshToken(UUID userId, UUID tenantId) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("tenant_id", tenantId.toString());
        claims.put("type", "refresh");
        claims.put("jti", UUID.randomUUID().toString());

        return buildToken(claims, userId.toString(), refreshTokenExpiry);
    }

    public String extractSubject(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    public UUID extractUserId(String token) {
        return UUID.fromString(extractSubject(token));
    }

    public UUID extractTenantId(String token) {
        return UUID.fromString(extractClaim(token, claims -> claims.get("tenant_id", String.class)));
    }

    public String extractRole(String token) {
        return extractClaim(token, claims -> claims.get("role", String.class));
    }

    public String extractEmail(String token) {
        return extractClaim(token, claims -> claims.get("email", String.class));
    }

    /**
     * Extract the JTI (JWT ID) claim — used for token revocation.
     */
    public String extractJti(String token) {
        return extractClaim(token, claims -> claims.get("jti", String.class));
    }

    /**
     * Extract permissions list from STAFF token.
     * Returns empty list if not present (non-STAFF tokens).
     */
    @SuppressWarnings("unchecked")
    public List<String> extractPermissions(String token) {
        return extractClaim(token, claims -> {
            Object perms = claims.get("permissions");
            if (perms instanceof List<?>) {
                return (List<String>) perms;
            }
            return Collections.emptyList();
        });
    }

    /**
     * Extract token expiration as Instant — used for revocation record.
     */
    public Instant extractExpirationInstant(String token) {
        Date exp = extractClaim(token, Claims::getExpiration);
        return exp.toInstant();
    }

    public boolean isTokenValid(String token) {
        try {
            extractAllClaims(token);
            return !isTokenExpired(token);
        } catch (Exception e) {
            return false;
        }
    }

    public boolean isTokenExpired(String token) {
        return extractExpiration(token).before(Date.from(Instant.now()));
    }

    private Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

    private <T> T extractClaim(String token, Function<Claims, T> resolver) {
        final Claims claims = extractAllClaims(token);
        return resolver.apply(claims);
    }

    private Claims extractAllClaims(String token) {
        return Jwts.parser()
                .verifyWith(getSigningKey())
                .build()
                .parseSignedClaims(token)
                .getPayload();
    }

    private String buildToken(Map<String, Object> extraClaims, String subject, long expirySeconds) {
        Instant now = Instant.now();
        return Jwts.builder()
                .claims(extraClaims)
                .subject(subject)
                .issuedAt(Date.from(now))
                .expiration(Date.from(now.plusSeconds(expirySeconds)))
                .signWith(getSigningKey())
                .compact();
    }

    private SecretKey getSigningKey() {
        byte[] keyBytes = Decoders.BASE64.decode(secretKey);
        return Keys.hmacShaKeyFor(keyBytes);
    }
}
