package kr.itn.itnhub.seed;

import kr.itn.itnhub.contact.Contact;
import kr.itn.itnhub.contact.ContactMapper;
import kr.itn.itnhub.org.Organization;
import kr.itn.itnhub.org.OrganizationMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

import java.io.InputStream;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Service
public class SeedService {

    private final SeedParser parser;
    private final OrganizationMapper mapper;
    private final ContactMapper contactMapper;
    private final TransactionTemplate transactionTemplate;

    public SeedService(SeedParser parser, OrganizationMapper mapper, ContactMapper contactMapper,
                        PlatformTransactionManager transactionManager) {
        this.parser = parser;
        this.mapper = mapper;
        this.contactMapper = contactMapper;
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }

    /**
     * 엑셀 파싱은 DB와 무관하므로 트랜잭션 밖에서 수행한다. 커넥션을 잡아두는 구간은
     * 기존 행 조회 + upsert 루프뿐이며, {@link TransactionTemplate}으로 그 구간만 감싼다.
     * (같은 빈의 @Transactional 메서드를 self-invocation으로 호출하면 프록시를 우회해
     * 트랜잭션이 아예 적용되지 않으므로 이 방식을 쓴다.)
     */
    public SeedReport seed(InputStream xlsx) {
        List<SeedRow> rows = parser.parse(xlsx);
        return transactionTemplate.execute(status -> seedRows(rows));
    }

    private SeedReport seedRows(List<SeedRow> rows) {
        // DB에 이미 있는 키와, 이번 업로드에서 먼저 처리된 키를 모두 "이미 존재함"으로 취급한다.
        // 그래야 한 시트에 같은 기관이 두 번 들어와도 두 번째는 created가 아니라 updated로 집계된다.
        Set<String> seen = new HashSet<>();
        for (Organization existing : mapper.findAll()) {
            seen.add(key(existing.getOrgNo(), existing.getOrgName()));
        }

        int created = 0;
        int updated = 0;

        for (SeedRow row : rows) {
            if (seen.add(key(row.orgNo(), row.orgName()))) {
                created++;
            } else {
                updated++;
            }
            mapper.upsertBySeed(toOrganization(row));
            linkApplicantContactIfMissing(row);
        }

        return new SeedReport(created, updated, rows.size());
    }

    /**
     * 시트에 담당자 이름이 있고 그 기관이 아직 신청기관 담당자가 배정되지 않았을 때만
     * 새 담당자를 만들어 연결한다. 이미 배정돼 있으면(담당자관리 화면에서 사람이 고쳤을 수도
     * 있는 값이므로) 절대 건드리지 않는다 - 재시드가 배정을 되돌리거나 중복 생성하면 안 된다.
     */
    private void linkApplicantContactIfMissing(SeedRow row) {
        if (row.managerName() == null) {
            return;
        }

        Organization saved = mapper.findByOrgNoAndOrgName(row.orgNo(), row.orgName());
        if (saved.getApplicantContactId() != null) {
            return;
        }

        Contact contact = new Contact();
        contact.setCategory("APPLICANT");
        contact.setName(row.managerName());
        contact.setAffiliation(row.orgName());
        contact.setDeptName(row.deptName());
        contact.setTitle(row.managerTitle());
        contact.setPhone(row.managerPhone());
        contact.setEmail(row.managerEmail());
        contactMapper.insert(contact);

        mapper.updateAssignments(saved.getId(), contact.getId(),
                saved.getMjContactId(), saved.getLawyerContactId(), saved.getLawyerAssignedDate());
    }

    private String key(String orgNo, String orgName) {
        return orgNo + " " + orgName;
    }

    private Organization toOrganization(SeedRow row) {
        Organization org = new Organization();
        org.setOrgNo(row.orgNo());
        org.setOrgName(row.orgName());
        org.setChannelSlug(row.channelSlug());
        return org;
    }
}
