package com.bidding.supplier.security.openapi;

import com.alibaba.fastjson2.JSON;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * Supplier account API codec delivered to the customer for payload decryption
 * and BCrypt password verification.
 */
public class BiddingSupplierAccountApiCodec
{
    private static final String PROTOCOL = "BSA";
    private static final String VERSION = "v1";
    private static final int IV_LENGTH = 12;
    private static final int GCM_TAG_BITS = 128;
    private static final SecureRandom SECURE_RANDOM = new SecureRandom();
    private static final BCryptPasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder();

    private final String keyId;
    private final String prefix;
    private final byte[] aesKey;

    public BiddingSupplierAccountApiCodec(String keyId, String aesKeyBase64)
    {
        if (keyId == null || !keyId.matches("^[A-Za-z0-9_-]{1,32}$"))
        {
            throw new IllegalArgumentException("Invalid supplier account API key id");
        }
        try
        {
            this.aesKey = Base64.getDecoder().decode(aesKeyBase64 == null ? "" : aesKeyBase64.trim());
        }
        catch (IllegalArgumentException exception)
        {
            throw new IllegalArgumentException("Invalid supplier account API AES key", exception);
        }
        if (aesKey.length != 32)
        {
            throw new IllegalArgumentException("Supplier account API AES key must be 32 bytes");
        }
        this.keyId = keyId;
        this.prefix = PROTOCOL + "." + VERSION + "." + keyId + ".";
    }

    public String decryptPayload(String payload)
    {
        try
        {
            String[] parts = parsePayload(payload);
            byte[] iv = Base64.getUrlDecoder().decode(parts[3]);
            byte[] ciphertextAndTag = Base64.getUrlDecoder().decode(parts[4]);
            if (iv.length != IV_LENGTH || ciphertextAndTag.length <= GCM_TAG_BITS / 8)
            {
                throw new IllegalArgumentException("Invalid supplier account API payload");
            }
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(aesKey, "AES"),
                    new GCMParameterSpec(GCM_TAG_BITS, iv));
            return new String(cipher.doFinal(ciphertextAndTag), StandardCharsets.UTF_8);
        }
        catch (IllegalArgumentException exception)
        {
            throw exception;
        }
        catch (Exception exception)
        {
            throw new IllegalArgumentException("Unable to decrypt supplier account API payload", exception);
        }
    }

    public <T> T decryptPayload(String payload, Class<T> targetType)
    {
        if (targetType == null)
        {
            throw new IllegalArgumentException("Target type is required");
        }
        return JSON.parseObject(decryptPayload(payload), targetType);
    }

    /**
     * Generate a BCrypt hash for a new or changed customer-platform password.
     */
    public String encodePassword(String rawPassword)
    {
        if (rawPassword == null)
        {
            throw new IllegalArgumentException("Raw password is required");
        }
        return PASSWORD_ENCODER.encode(rawPassword);
    }

    public boolean matchesPassword(String rawPassword, String bcryptPasswordHash)
    {
        if (rawPassword == null || bcryptPasswordHash == null || bcryptPasswordHash.isBlank())
        {
            return false;
        }
        try
        {
            return PASSWORD_ENCODER.matches(rawPassword, bcryptPasswordHash);
        }
        catch (IllegalArgumentException exception)
        {
            return false;
        }
    }

    String encryptPayload(Object payload)
    {
        try
        {
            byte[] iv = new byte[IV_LENGTH];
            SECURE_RANDOM.nextBytes(iv);
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(aesKey, "AES"),
                    new GCMParameterSpec(GCM_TAG_BITS, iv));
            byte[] ciphertextAndTag = cipher.doFinal(JSON.toJSONBytes(payload));
            Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding();
            return prefix + encoder.encodeToString(iv) + "." + encoder.encodeToString(ciphertextAndTag);
        }
        catch (Exception exception)
        {
            throw new IllegalStateException("Unable to encrypt supplier account API payload", exception);
        }
    }

    private String[] parsePayload(String payload)
    {
        if (payload == null || !payload.startsWith(prefix))
        {
            throw new IllegalArgumentException("Invalid supplier account API payload");
        }
        String[] parts = payload.split("\\.", 5);
        if (parts.length != 5 || !PROTOCOL.equals(parts[0]) || !VERSION.equals(parts[1])
                || !keyId.equals(parts[2]))
        {
            throw new IllegalArgumentException("Invalid supplier account API payload");
        }
        return parts;
    }
}
