package de.effigenix.infrastructure;

import com.fasterxml.jackson.databind.ObjectMapper;
import de.effigenix.domain.usermanagement.RoleName;
import de.effigenix.domain.usermanagement.UserStatus;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.simple.JdbcClient;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.Set;
import java.util.UUID;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc
@ActiveProfiles("test")
@Transactional
public abstract class AbstractIntegrationTest {

    protected static final String BCRYPT_PASS123 = "$2a$10$4tNfKjz7w.67G72CVop9CuuzBh5vrkYJki8lZ66ZwwLtQjlrm6hHy";
    protected static final String TEST_PASSWORD = "Pass123";

    @Autowired
    protected MockMvc mockMvc;

    @Autowired
    protected ObjectMapper objectMapper;

    @Autowired
    protected JdbcClient jdbc;

    @Value("${jwt.secret}")
    protected String jwtSecret;

    @Value("${jwt.expiration}")
    protected long jwtExpiration;

    protected String generateToken(String userId, String username, String permissions) {
        long now = System.currentTimeMillis();
        SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor(
                jwtSecret.getBytes(StandardCharsets.UTF_8));
        return Jwts.builder()
                .subject(userId)
                .claim("username", username)
                .claim("permissions", permissions)
                .issuedAt(new Date(now))
                .expiration(new Date(now + jwtExpiration))
                .signWith(key)
                .compact();
    }

    protected String generateExpiredToken(String userId, String username) {
        long now = System.currentTimeMillis();
        SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor(
                jwtSecret.getBytes(StandardCharsets.UTF_8));
        return Jwts.builder()
                .subject(userId)
                .claim("username", username)
                .claim("permissions", "")
                .issuedAt(new Date(now - 10000))
                .expiration(new Date(now - 5000))
                .signWith(key)
                .compact();
    }

    protected String generateRefreshToken(String userId, String username) {
        long now = System.currentTimeMillis();
        SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor(
                jwtSecret.getBytes(StandardCharsets.UTF_8));
        return Jwts.builder()
                .subject(userId)
                .claim("username", username)
                .claim("type", "refresh")
                .issuedAt(new Date(now))
                .expiration(new Date(now + 7200000))
                .signWith(key)
                .compact();
    }

    protected String createRole(RoleName roleName, String description) {
        var existing = jdbc.sql("SELECT id FROM roles WHERE name = :name")
                .param("name", roleName.name())
                .query(String.class)
                .optional();
        if (existing.isPresent()) {
            return existing.get();
        }
        String id = UUID.randomUUID().toString();
        jdbc.sql("INSERT INTO roles (id, name, description) VALUES (:id, :name, :description)")
                .param("id", id)
                .param("name", roleName.name())
                .param("description", description)
                .update();
        return id;
    }

    protected String createUser(String username, String email, Set<String> roleIds, String branchId) {
        String id = UUID.randomUUID().toString();
        jdbc.sql("""
                INSERT INTO users (id, username, email, password_hash, branch_id, status, created_at)
                VALUES (:id, :username, :email, :passwordHash, :branchId, :status, :createdAt)
                """)
                .param("id", id)
                .param("username", username)
                .param("email", email)
                .param("passwordHash", BCRYPT_PASS123)
                .param("branchId", branchId)
                .param("status", UserStatus.ACTIVE.name())
                .param("createdAt", OffsetDateTime.now(ZoneOffset.UTC))
                .update();

        for (String roleId : roleIds) {
            jdbc.sql("INSERT INTO user_roles (user_id, role_id) VALUES (:userId, :roleId)")
                    .param("userId", id)
                    .param("roleId", roleId)
                    .update();
        }

        return id;
    }

    protected String createArticleId() {
        String categoryId = UUID.randomUUID().toString();
        jdbc.sql("INSERT INTO product_categories (id, name) VALUES (:id, :name)")
                .param("id", categoryId)
                .param("name", "TestCat-" + categoryId.substring(0, 8))
                .update();

        String articleId = UUID.randomUUID().toString();
        var now = OffsetDateTime.now(ZoneOffset.UTC);
        jdbc.sql("""
                INSERT INTO articles (id, name, article_number, category_id, status, created_at, updated_at)
                VALUES (:id, :name, :articleNumber, :categoryId, :status, :createdAt, :updatedAt)
                """)
                .param("id", articleId)
                .param("name", "TestArticle-" + UUID.randomUUID().toString().substring(0, 8))
                .param("articleNumber", "ART-" + UUID.randomUUID().toString().substring(0, 8))
                .param("categoryId", categoryId)
                .param("status", "ACTIVE")
                .param("createdAt", now)
                .param("updatedAt", now)
                .update();

        return articleId;
    }
}
