package kr.itn.itnhub.feed;

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 kr.itn.itnhub.org.OrgNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * 기관관리 상세 화면의 채팅 탭이 쓰는 서비스. 조회뿐 아니라 메시지 전송·공지 등록/해제까지
 * 담당한다. Mattermost를 매번 실시간으로 읽고 쓰는 통로이므로 {@code @Transactional}을
 * 걸지 않는다 - DB 조회는 채널 id를 얻기 위한 짧은 findById 한 번뿐이고, 외부 API 호출을
 * 트랜잭션 안에 묶어둘 이유가 없다.
 *
 * <p>공지 정책(정책 A - 단일 활성 공지)은 {@link #sendMessage}에서 처리한다: 게시글을
 * 올린 뒤, 기존에 고정돼 있던 게시글을 전부 해제하고, 새 게시글을 고정하고, 채널 헤더를
 * 공지 요약 + 원문 permalink로 바꾼다. 게시글 전송 자체가 아니라 이 뒤처리(고정/헤더) 중
 * 하나라도 실패하면 - 게시글은 이미 채널 멤버에게 전달된 뒤이므로 - 실패로 보고하지 않고
 * {@link SendResult#noticeWarning()}에 경고만 담아 돌려준다.</p>
 */
@Service
public class ChannelFeedService {

    /** 게시물 탭 한 번에 보여줄 최근 게시글 수. */
    private static final int RECENT_POSTS_PAGE_SIZE = 60;

    /** 공지 헤더에 넣을 요약문 최대 길이(첫 줄 기준). */
    private static final int NOTICE_SUMMARY_MAX_LEN = 150;

    private final OrganizationMapper orgMapper;
    private final MattermostClient mattermost;
    private final MattermostProperties mattermostProps;

    public ChannelFeedService(OrganizationMapper orgMapper, MattermostClient mattermost,
                               MattermostProperties mattermostProps) {
        this.orgMapper = orgMapper;
        this.mattermost = mattermost;
        this.mattermostProps = mattermostProps;
    }

    public List<PostView> posts(Long orgId, String channel) {
        String channelId = resolveChannelId(orgId, channel);
        return mattermost.getRecentPosts(channelId, RECENT_POSTS_PAGE_SIZE);
    }

    public List<FileView> files(Long orgId, String channel) {
        String channelId = resolveChannelId(orgId, channel);
        return mattermost.collectChannelFiles(channelId);
    }

    /**
     * 메시지를 보내고, {@code notice}가 참이면 정책 A(단일 활성 공지)를 적용한다.
     * 파일 업로드·게시글 생성이 실패하면 그대로 예외가 전파된다(전송 자체가 실패한 것).
     */
    public SendResult sendMessage(Long orgId, String channel, String message,
                                   List<UploadedFile> files, boolean notice) {
        String channelId = resolveChannelId(orgId, channel);

        List<String> fileIds = new ArrayList<>();
        for (UploadedFile file : files) {
            fileIds.add(mattermost.uploadFile(channelId, file.filename(), file.data(), file.contentType()));
        }

        String postId = mattermost.createPost(channelId, message, fileIds);
        PostView post = fetchPost(channelId, postId, message);

        if (!notice) {
            return new SendResult(post, null);
        }

        try {
            for (PostView pinned : mattermost.getPinnedPosts(channelId)) {
                mattermost.unpinPost(pinned.id());
            }
            mattermost.pinPost(postId);
            mattermost.updateChannelHeader(channelId, noticeHeader(message, files, postId));
            return new SendResult(post, null);
        } catch (MattermostException e) {
            return new SendResult(post, "공지 등록 처리 중 일부가 실패했습니다: " + e.getMessage());
        }
    }

    /** 채널의 "현재 공지" = 가장 최근에 고정된, 시스템 메시지가 아닌 게시글. 없으면 null. */
    public PostView currentNotice(Long orgId, String channel) {
        String channelId = resolveChannelId(orgId, channel);
        return mattermost.getPinnedPosts(channelId).stream()
                .filter(p -> !p.system())
                .findFirst()
                .orElse(null);
    }

    /** 공지 해제: 고정된 게시글을 전부 해제하고 헤더를 비운다. 원본 글은 채팅 기록에 남는다. */
    public void clearNotice(Long orgId, String channel) {
        String channelId = resolveChannelId(orgId, channel);
        for (PostView pinned : mattermost.getPinnedPosts(channelId)) {
            mattermost.unpinPost(pinned.id());
        }
        mattermost.updateChannelHeader(channelId, "");
    }

    /**
     * 방금 만든 게시글을 다시 조회해 표시용 정보(작성자 표시명 등)까지 채워진 {@link PostView}로
     * 돌려준다. 조회가 비어 있는 예외적인 경우를 대비해 최소한의 값으로 대체한다.
     */
    private PostView fetchPost(String channelId, String postId, String message) {
        return mattermost.getRecentPosts(channelId, 1).stream()
                .filter(p -> p.id().equals(postId))
                .findFirst()
                .orElseGet(() -> new PostView(postId, "", message == null ? "" : message,
                        System.currentTimeMillis(), false, List.of()));
    }

    /**
     * 공지 헤더 문자열: {@code 📢 공지: {요약} — [자세히]({permalink})}.
     * 요약은 메시지 첫 줄을 150자로 자른 값이고, 메시지가 비어 있으면 첫 첨부파일 이름을 쓴다.
     */
    private String noticeHeader(String message, List<UploadedFile> files, String postId) {
        String summary = noticeSummary(message, files);
        String permalink = mattermostProps.baseUrl() + "/" + mattermostProps.teamName() + "/pl/" + postId;
        return "📢 공지: " + summary + " — [자세히](" + permalink + ")";
    }

    private String noticeSummary(String message, List<UploadedFile> files) {
        String firstLine = message == null ? "" : message.strip().split("\\R", 2)[0];
        if (firstLine.isBlank() && !files.isEmpty()) {
            firstLine = files.get(0).filename();
        }
        if (firstLine.length() > NOTICE_SUMMARY_MAX_LEN) {
            firstLine = firstLine.substring(0, NOTICE_SUMMARY_MAX_LEN);
        }
        return firstLine;
    }

    private String resolveChannelId(Long orgId, String channel) {
        Organization org = orgMapper.findById(orgId);
        if (org == null) {
            throw new OrgNotFoundException("기관을 찾을 수 없습니다: " + orgId);
        }

        String channelId = switch (channel) {
            case "mj" -> org.getChannelIdMj();
            case "law" -> org.getChannelIdLaw();
            default -> throw new ChannelNotReadyException("알 수 없는 채널 구분입니다: " + channel);
        };

        if (channelId == null || channelId.isBlank()) {
            throw new ChannelNotReadyException("해당 채널이 아직 생성되지 않았습니다.");
        }
        return channelId;
    }
}
