package com.zioinfo.kintex.auth;

import com.zioinfo.kintex.common.error.ApiException;
import com.zioinfo.kintex.common.error.ErrorCode;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Service;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * JWT 발급/검증 — HS256. 시크릿은 {@link AuthProperties}(env JWT_SECRET)에서만 로드.
 * 토큰은 사용자 신원 + 행사별 역할 맵을 담는다(행사 단위 RBAC).
 */
@Service
public class JwtService {

    private final SecretKey key;
    private final long ttlSeconds;

    public JwtService(AuthProperties props) {
        this.key = Keys.hmacShaKeyFor(props.getJwtSecret().getBytes(StandardCharsets.UTF_8));
        this.ttlSeconds = props.getAccessTokenTtlSeconds();
    }

    /** 기준 테넌트(#1 KINTEX) — TenantContext.DEFAULT_TENANT와 일치. tid 미지정·공백 시 폴백(회귀 0). */
    private static final String DEFAULT_TENANT = "KINTEX";

    /** 테넌트 코드 정규화(trim+대문자) — 소문자·레거시 tid를 표준 대문자 코드로 매칭. */
    private static String normalizeTenant(String tid) {
        String v = com.zioinfo.kintex.tenant.TenantContext.normalize(tid);
        return v == null ? DEFAULT_TENANT : v;
    }

    /**
     * 액세스 토큰 발급. eventRoles: 행사ID→역할. roleCode: 전역 역할(app_user.role_code, null 허용).
     * tenantId: 소속 테넌트(멀티테넌시 2단계) — {@code tid} 클레임으로 순증(§1A-3-2). null/공백이면 기준 테넌트.
     */
    public String issue(String userId, String displayName,
                        Map<String, EventRole> eventRoles, boolean hallManager, String roleCode,
                        String tenantId) {
        Instant now = Instant.now();
        Map<String, String> rolesClaim = new LinkedHashMap<>();
        if (eventRoles != null) {
            eventRoles.forEach((eid, role) -> rolesClaim.put(eid, role.name()));
        }
        var builder = Jwts.builder()
                .subject(userId)
                .claim("name", displayName)
                .claim("roles", rolesClaim)
                .claim("hm", hallManager)
                .claim("tid", normalizeTenant(tenantId));
        if (roleCode != null && !roleCode.isBlank()) {
            builder.claim("rc", roleCode); // 순증 클레임 — /me 전역 역할 복원용
        }
        return builder
                .issuedAt(Date.from(now))
                .expiration(Date.from(now.plusSeconds(ttlSeconds)))
                .signWith(key)
                .compact();
    }

    /** 하위호환 오버로드 — 테넌트 미지정 경로용(tid=기준 테넌트). */
    public String issue(String userId, String displayName,
                        Map<String, EventRole> eventRoles, boolean hallManager, String roleCode) {
        return issue(userId, displayName, eventRoles, hallManager, roleCode, DEFAULT_TENANT);
    }

    /** 하위호환 오버로드 — 전역 역할·테넌트 미상 경로용(roleCode=null). */
    public String issue(String userId, String displayName,
                        Map<String, EventRole> eventRoles, boolean hallManager) {
        return issue(userId, displayName, eventRoles, hallManager, null, DEFAULT_TENANT);
    }

    /** 토큰 검증 후 principal 복원. 실패 시 401. */
    @SuppressWarnings("unchecked")
    public KintexPrincipal verify(String token) {
        try {
            Claims c = Jwts.parser().verifyWith(key).build()
                    .parseSignedClaims(token).getPayload();
            Map<String, EventRole> roles = new HashMap<>();
            Object raw = c.get("roles");
            if (raw instanceof Map<?, ?> m) {
                m.forEach((k, v) -> roles.put(String.valueOf(k), EventRole.valueOf(String.valueOf(v))));
            }
            boolean hm = Boolean.TRUE.equals(c.get("hm", Boolean.class));
            String roleCode = c.get("rc", String.class);
            // tid 부재(2단계 이전 발급 토큰)·소문자('kintex') = 대문자 정규화 후 기준 테넌트 'KINTEX'로 복원(하위호환·회귀 0).
            String tenantId = normalizeTenant(c.get("tid", String.class));
            return new KintexPrincipal(c.getSubject(), c.get("name", String.class), roles, hm, roleCode, tenantId);
        } catch (JwtException | IllegalArgumentException e) {
            throw new ApiException(ErrorCode.UNAUTHORIZED, "토큰이 유효하지 않거나 만료되었습니다.");
        }
    }

    public long ttlSeconds() {
        return ttlSeconds;
    }
}
