package kr.itn.itnhub.provision;

import kr.itn.itnhub.config.MattermostProperties;
import kr.itn.itnhub.mattermost.MattermostClient;
import kr.itn.itnhub.mattermost.MattermostException;
import kr.itn.itnhub.org.Organization;
import kr.itn.itnhub.org.OrganizationMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.Optional;

/**
 * 기관의 채널 2개(문정원/법률검토)를 보장한다. VBA M07의 이식.
 *
 * 판정 순서 (한쪽 채널당):
 *   1. DB에 Channel ID가 있으면 그대로 둔다        → CACHED
 *   2. 서버에 내부명으로 채널이 있으면 ID를 가져온다 → RECOVERED
 *   3. 표시명으로 있으면 ID를 가져온다              → RECOVERED
 *   4. 없을 때만 새로 만든다                       → CREATED
 *
 * 트랜잭션을 쓰지 않는다. 외부 API 호출이라 롤백이 불가능하고,
 * 한쪽이 실패해도 성공한 쪽의 ID는 반드시 남아야 재시도가 안전해지기 때문이다.
 * 절대 @Transactional을 붙이지 말 것.
 */
@Service
public class ChannelProvisionService {

    private static final Logger log = LoggerFactory.getLogger(ChannelProvisionService.class);

    private final MattermostClient mattermost;
    private final OrganizationMapper mapper;
    private final MattermostProperties props;

    public ChannelProvisionService(MattermostClient mattermost,
                                   OrganizationMapper mapper,
                                   MattermostProperties props) {
        this.mattermost = mattermost;
        this.mapper = mapper;
        this.props = props;
    }

    public ProvisionResult provision(Long orgId) {
        Organization org = mapper.findById(orgId);
        if (org == null) {
            throw new IllegalArgumentException("기관을 찾을 수 없습니다: " + orgId);
        }

        if (!org.hasRequiredContact()) {
            return new ProvisionResult(
                    ProvisionOutcome.FAILED,
                    ProvisionOutcome.FAILED,
                    "담당자 정보가 채워지지 않아 채널을 만들 수 없습니다.");
        }

        StringBuilder messages = new StringBuilder();

        ProvisionOutcome mj = ensure(org, ChannelKind.MJ,
                org.getChannelIdMj(), props.channelNameMj(), messages);

        ProvisionOutcome law = ensure(org, ChannelKind.LAW,
                org.getChannelIdLaw(), props.channelNameLaw(), messages);

        return new ProvisionResult(mj, law, messages.toString().trim());
    }

    private ProvisionOutcome ensure(Organization org,
                                    ChannelKind kind,
                                    String storedChannelId,
                                    String label,
                                    StringBuilder messages) {

        if (storedChannelId != null && !storedChannelId.isBlank()) {
            return ProvisionOutcome.CACHED;
        }

        String internalName = ChannelNaming.internalName(
                org.getOrgNo(), org.getOrgName(), kind);
        String displayName = ChannelNaming.displayName(
                org.getOrgNo(), org.getOrgName(), label);

        try {
            Optional<String> recovered = mattermost.findChannelIdByInternalName(internalName);
            if (recovered.isEmpty()) {
                recovered = mattermost.findChannelIdByDisplayName(displayName);
            }

            if (recovered.isPresent()) {
                applyChannelId(org.getId(), kind, recovered.get());
                return ProvisionOutcome.RECOVERED;
            }

            String created = mattermost.createPrivateChannel(internalName, displayName);
            applyChannelId(org.getId(), kind, created);
            return ProvisionOutcome.CREATED;

        } catch (MattermostException e) {
            log.warn("채널 처리 실패 org={} channel={}", org.getOrgName(), displayName, e);
            messages.append("[").append(label).append("] ")
                    .append(e.getMessage()).append(System.lineSeparator());
            return ProvisionOutcome.FAILED;
        }
    }

    /**
     * Channel ID를 저장할 컬럼(mj/law)을 결정하는 유일한 지점.
     * 문자열 키 기반 동적 분기 대신 정적 타입(ChannelKind)으로만 분기하여,
     * 잘못된 컬럼에 쓰는 실수를 컴파일 타임에 막는다.
     */
    private void applyChannelId(Long orgId, ChannelKind kind, String channelId) {
        if (kind == ChannelKind.MJ) {
            mapper.updateChannelIdMj(orgId, channelId);
        } else {
            mapper.updateChannelIdLaw(orgId, channelId);
        }
    }
}
