package com.cameleer.server.app.alerting.storage;

import com.cameleer.server.app.AbstractPostgresIT;
import com.cameleer.server.app.search.ClickHouseLogStore;
import com.cameleer.server.core.alerting.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;

import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;

class PostgresAlertRuleRepositoryIT extends AbstractPostgresIT {

    @MockBean(name = "clickHouseLogStore")    ClickHouseLogStore    clickHouseLogStore;

    private PostgresAlertRuleRepository repo;
    private UUID envId;

    @BeforeEach
    void setup() {
        repo = new PostgresAlertRuleRepository(jdbcTemplate, new ObjectMapper());
        envId = UUID.randomUUID();
        jdbcTemplate.update(
            "INSERT INTO environments (id, slug, display_name) VALUES (?, ?, ?)",
            envId, "test-env-" + UUID.randomUUID(), "Test Env");
        jdbcTemplate.update(
            "INSERT INTO users (user_id, provider, email) VALUES ('test-user', 'local', 'test@example.com')" +
            " ON CONFLICT (user_id) DO NOTHING");
    }

    @AfterEach
    void cleanup() {
        jdbcTemplate.update("DELETE FROM alert_rules WHERE environment_id = ?", envId);
        jdbcTemplate.update("DELETE FROM environments WHERE id = ?", envId);
        jdbcTemplate.update("DELETE FROM users WHERE user_id = 'test-user'");
    }

    @Test
    void saveAndFindByIdRoundtrip() {
        var rule = newRule(List.of());
        repo.save(rule);
        var found = repo.findById(rule.id()).orElseThrow();
        assertThat(found.name()).isEqualTo(rule.name());
        assertThat(found.condition()).isInstanceOf(AgentStateCondition.class);
        assertThat(found.severity()).isEqualTo(AlertSeverity.WARNING);
        assertThat(found.conditionKind()).isEqualTo(ConditionKind.AGENT_STATE);
    }

    @Test
    void findRuleIdsByOutboundConnectionId() {
        var connId = UUID.randomUUID();
        var wb = new WebhookBinding(UUID.randomUUID(), connId, null, Map.of());
        var rule = newRule(List.of(wb));
        repo.save(rule);

        List<UUID> ids = repo.findRuleIdsByOutboundConnectionId(connId);
        assertThat(ids).containsExactly(rule.id());

        assertThat(repo.findRuleIdsByOutboundConnectionId(UUID.randomUUID())).isEmpty();
    }

    @Test
    void saveTargets_roundtrip() {
        // Rule saved with a USER target and a ROLE target
        UUID ruleId = UUID.randomUUID();
        AlertRuleTarget userTarget = new AlertRuleTarget(UUID.randomUUID(), ruleId, TargetKind.USER, "alice");
        AlertRuleTarget roleTarget = new AlertRuleTarget(UUID.randomUUID(), ruleId, TargetKind.ROLE, "OPERATOR");
        var rule = newRuleWithId(ruleId, List.of(), List.of(userTarget, roleTarget));

        repo.save(rule);

        // findById must return the targets that were persisted by saveTargets()
        var found = repo.findById(ruleId).orElseThrow();
        assertThat(found.targets()).hasSize(2);
        assertThat(found.targets()).extracting(AlertRuleTarget::targetId)
                .containsExactlyInAnyOrder("alice", "OPERATOR");
        assertThat(found.targets()).extracting(t -> t.kind().name())
                .containsExactlyInAnyOrder("USER", "ROLE");
    }

    @Test
    void saveTargets_updateReplacesExistingTargets() {
        // Save rule with one target
        UUID ruleId = UUID.randomUUID();
        AlertRuleTarget initial = new AlertRuleTarget(UUID.randomUUID(), ruleId, TargetKind.USER, "bob");
        var rule = newRuleWithId(ruleId, List.of(), List.of(initial));
        repo.save(rule);

        // Update: replace with a different target
        AlertRuleTarget updated = new AlertRuleTarget(UUID.randomUUID(), ruleId, TargetKind.GROUP, "team-ops");
        var updated_rule = newRuleWithId(ruleId, List.of(), List.of(updated));
        repo.save(updated_rule);

        var found = repo.findById(ruleId).orElseThrow();
        assertThat(found.targets()).hasSize(1);
        assertThat(found.targets().get(0).targetId()).isEqualTo("team-ops");
        assertThat(found.targets().get(0).kind()).isEqualTo(TargetKind.GROUP);
    }

    @Test
    void claimDueRulesAtomicSkipLocked() {
        var rule = newRule(List.of());
        repo.save(rule);

        List<AlertRule> claimed = repo.claimDueRules("instance-A", 10, 30);
        assertThat(claimed).hasSize(1);

        // Second claimant sees nothing until first releases or TTL expires
        List<AlertRule> second = repo.claimDueRules("instance-B", 10, 30);
        assertThat(second).isEmpty();
    }

    private AlertRule newRule(List<WebhookBinding> webhooks) {
        return newRuleWithId(UUID.randomUUID(), webhooks, List.of());
    }

    private AlertRule newRuleWithId(UUID id, List<WebhookBinding> webhooks, List<AlertRuleTarget> targets) {
        return new AlertRule(
            id, envId, "rule-" + id, "desc",
            AlertSeverity.WARNING, true, ConditionKind.AGENT_STATE,
            new AgentStateCondition(new AlertScope(null, null, null), "DEAD", 60),
            60, 0, 60, "t", "m", webhooks, targets,
            Instant.now().minusSeconds(10), null, null, Map.of(),
            Instant.now(), "test-user", Instant.now(), "test-user");
    }
}
