package de.assecutor.votianlt.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import de.assecutor.votianlt.model.AppUser;
import de.assecutor.votianlt.model.Barcode;
import de.assecutor.votianlt.model.Comment;
import de.assecutor.votianlt.model.Customer;
import de.assecutor.votianlt.model.DeliveryStation;
import de.assecutor.votianlt.model.InvoiceTemplate;
import de.assecutor.votianlt.model.Job;
import de.assecutor.votianlt.model.JobServiceSelection;
import de.assecutor.votianlt.model.JobStatus;
import de.assecutor.votianlt.model.Language;
import de.assecutor.votianlt.model.Photo;
import de.assecutor.votianlt.model.Service;
import de.assecutor.votianlt.model.Signature;
import de.assecutor.votianlt.model.TaskTemplate;
import de.assecutor.votianlt.model.User;
import de.assecutor.votianlt.model.invoices.CustomerInvoice;
import de.assecutor.votianlt.model.task.BaseTask;
import de.assecutor.votianlt.pages.domain.CustomerRepository;
import de.assecutor.votianlt.repository.AppUserRepository;
import de.assecutor.votianlt.repository.BarcodeRepository;
import de.assecutor.votianlt.repository.CargoItemRepository;
import de.assecutor.votianlt.repository.CommentRepository;
import de.assecutor.votianlt.repository.CustomerInvoiceRepository;
import de.assecutor.votianlt.repository.InvoiceTemplateRepository;
import de.assecutor.votianlt.repository.JobHistoryRepository;
import de.assecutor.votianlt.repository.JobRepository;
import de.assecutor.votianlt.repository.LocationPositionRepository;
import de.assecutor.votianlt.repository.MessageRepository;
import de.assecutor.votianlt.repository.PhotoRepository;
import de.assecutor.votianlt.repository.ServiceRepository;
import de.assecutor.votianlt.repository.SignatureRepository;
import de.assecutor.votianlt.repository.TaskRepository;
import de.assecutor.votianlt.repository.TaskTemplateRepository;
import de.assecutor.votianlt.repository.UserInvoiceDataRepository;
import de.assecutor.votianlt.repository.UserRepository;
import de.assecutor.votianlt.security.SessionAuthenticationService;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.transaction.annotation.Transactional;

@org.springframework.stereotype.Service
@RequiredArgsConstructor
@Transactional
@Slf4j
public class DemoModeService {

    public static final String DEMO_USERNAME = "demo";
    public static final String DEMO_PASSWORD = "demo";
    private static final String DEMO_FIRST_NAME = "Demo";
    private static final String DEMO_LAST_NAME = "Benutzer";
    private static final String DEMO_INVOICE_TEMPLATE_RESOURCE = "templates/demo_invoice_template_default.json";

    private final DemoSessionRegistry demoSessionRegistry;
    private final SessionAuthenticationService sessionAuthenticationService;
    private final UserRepository userRepository;
    private final CustomerRepository customerRepository;
    private final JobRepository jobRepository;
    private final CargoItemRepository cargoItemRepository;
    private final TaskRepository taskRepository;
    private final JobHistoryRepository jobHistoryRepository;
    private final CustomerInvoiceRepository customerInvoiceRepository;
    private final ServiceRepository serviceRepository;
    private final AppUserRepository appUserRepository;
    private final TaskTemplateRepository taskTemplateRepository;
    private final InvoiceTemplateRepository invoiceTemplateRepository;
    private final UserInvoiceDataRepository userInvoiceDataRepository;
    private final MessageRepository messageRepository;
    private final LocationPositionRepository locationPositionRepository;
    private final PhotoRepository photoRepository;
    private final SignatureRepository signatureRepository;
    private final BarcodeRepository barcodeRepository;
    private final CommentRepository commentRepository;
    private final PasswordEncoder passwordEncoder;

    public static boolean isDemoUsername(String username) {
        return username != null && DEMO_USERNAME.equalsIgnoreCase(username.trim());
    }

    public void ensureDemoUser() {
        User demoUser = userRepository.findByEmail(DEMO_USERNAME).orElseGet(User::new);
        applyDemoUserDefaults(demoUser);
        userRepository.save(demoUser);
    }

    public boolean tryPrepareDemoSession(String sessionId) {
        if (!demoSessionRegistry.acquire(sessionId)) {
            return false;
        }

        try {
            ensureDemoUser();
            cleanupDemoOwnedData();
            resetDemoUserProfile();
            seedDemoData();
            return true;
        } catch (RuntimeException ex) {
            demoSessionRegistry.release(sessionId);
            throw ex;
        }
    }

    public void cleanupCurrentSessionIfOwned() {
        sessionAuthenticationService.getCurrentSessionId().ifPresent(this::cleanupAndReleaseIfOwned);
    }

    public void cleanupAndReleaseIfOwned(String sessionId) {
        if (!demoSessionRegistry.isHeldBy(sessionId)) {
            return;
        }

        try {
            cleanupDemoOwnedData();
            resetDemoUserProfile();
        } catch (RuntimeException ex) {
            log.warn("Demo cleanup failed for session {}: {}", sessionId, ex.getMessage(), ex);
        } finally {
            demoSessionRegistry.release(sessionId);
        }
    }

    public void resetDemoUserProfile() {
        User demoUser = getDemoUser();
        applyDemoUserDefaults(demoUser);
        userRepository.save(demoUser);
    }

    public void cleanupDemoOwnedData() {
        Optional<User> demoUserOptional = userRepository.findByEmail(DEMO_USERNAME);
        if (demoUserOptional.isEmpty()) {
            return;
        }

        User demoUser = demoUserOptional.get();
        ObjectId demoUserId = demoUser.getId();
        if (demoUserId == null) {
            return;
        }

        String demoUserIdHex = demoUserId.toHexString();

        List<AppUser> demoAppUsers = appUserRepository.findByErstelltVon(demoUserId);
        cleanupDemoAppUserData(demoAppUsers);

        List<Job> demoJobs = jobRepository.findByCreatedBy(demoUserIdHex);
        cleanupDemoJobData(demoJobs);

        List<Customer> demoCustomers = customerRepository.findByOwner(demoUserId);
        if (!demoCustomers.isEmpty()) {
            customerRepository.deleteAll(demoCustomers);
        }

        serviceRepository.deleteByUserId(demoUserIdHex);

        List<TaskTemplate> demoTaskTemplates = taskTemplateRepository.findByUserIdOrderByTemplateNameAsc(demoUserId);
        if (!demoTaskTemplates.isEmpty()) {
            taskTemplateRepository.deleteAll(demoTaskTemplates);
        }

        invoiceTemplateRepository.deleteByUserId(demoUserIdHex);
        userInvoiceDataRepository.deleteByUserId(demoUserId);

        List<CustomerInvoice> demoInvoices = customerInvoiceRepository.findByUserId(demoUserIdHex);
        if (!demoInvoices.isEmpty()) {
            customerInvoiceRepository.deleteAll(demoInvoices);
        }

        if (!demoAppUsers.isEmpty()) {
            appUserRepository.deleteAll(demoAppUsers);
        }
    }

    public void seedDemoData() {
        User demoUser = getDemoUser();
        List<Service> services = seedDemoServices(demoUser);
        List<Customer> customers = seedDemoCustomers(demoUser);
        seedDemoJobs(demoUser, customers, services);
        seedDemoInvoiceTemplate(demoUser);
    }

    private void cleanupDemoAppUserData(List<AppUser> demoAppUsers) {
        for (AppUser appUser : demoAppUsers) {
            String appUserId = appUser.getIdAsString();
            if (appUserId == null || appUserId.isBlank()) {
                continue;
            }

            var messages = messageRepository.findByReceiverOrderByCreatedAtAsc(appUserId);
            if (!messages.isEmpty()) {
                messageRepository.deleteAll(messages);
            }

            var positions = locationPositionRepository.findByAppUserIdOrderByTimestampDesc(appUserId);
            if (!positions.isEmpty()) {
                locationPositionRepository.deleteAll(positions);
            }
        }
    }

    private void cleanupDemoJobData(List<Job> demoJobs) {
        if (demoJobs.isEmpty()) {
            return;
        }

        Set<ObjectId> stationIds = new LinkedHashSet<>();
        Set<ObjectId> jobIds = new LinkedHashSet<>();
        Set<String> jobIdStrings = new LinkedHashSet<>();
        for (Job job : demoJobs) {
            if (job == null || job.getId() == null) {
                continue;
            }
            jobIds.add(job.getId());
            jobIdStrings.add(job.getIdAsString());
            if (job.getDeliveryStations() == null) {
                continue;
            }
            for (DeliveryStation station : job.getDeliveryStations()) {
                if (station != null && station.getStationId() != null) {
                    stationIds.add(station.getStationId());
                }
            }
        }

        Map<ObjectId, BaseTask> tasksById = new LinkedHashMap<>();
        if (!stationIds.isEmpty()) {
            for (BaseTask task : taskRepository.findByStationIdIn(new ArrayList<>(stationIds))) {
                if (task != null && task.getId() != null) {
                    tasksById.put(task.getId(), task);
                }
            }
        }
        for (ObjectId jobId : jobIds) {
            for (BaseTask task : taskRepository.findByJobIdOrderByTaskOrderAsc(jobId)) {
                if (task != null && task.getId() != null) {
                    tasksById.put(task.getId(), task);
                }
            }
        }

        cleanupTaskArtifacts(tasksById.keySet());

        if (!tasksById.isEmpty()) {
            taskRepository.deleteAll(tasksById.values());
        }

        for (ObjectId jobId : jobIds) {
            var cargoItems = cargoItemRepository.findByJobId(jobId);
            if (!cargoItems.isEmpty()) {
                cargoItemRepository.deleteAll(cargoItems);
            }
            jobHistoryRepository.deleteByJobId(jobId);
        }

        List<CustomerInvoice> invoicesToDelete = new ArrayList<>();
        for (String jobId : jobIdStrings) {
            customerInvoiceRepository.findByJobId(jobId).ifPresent(invoicesToDelete::add);
        }
        if (!invoicesToDelete.isEmpty()) {
            customerInvoiceRepository.deleteAll(dedupeInvoices(invoicesToDelete));
        }

        jobRepository.deleteAll(demoJobs);
    }

    private void cleanupTaskArtifacts(Collection<ObjectId> taskIds) {
        for (ObjectId taskId : taskIds) {
            if (taskId == null) {
                continue;
            }

            List<Photo> photos = photoRepository.findByTaskId(taskId);
            if (!photos.isEmpty()) {
                photoRepository.deleteAll(photos);
            }

            List<Signature> signatures = signatureRepository.findByTaskId(taskId);
            if (!signatures.isEmpty()) {
                signatureRepository.deleteAll(signatures);
            }

            List<Barcode> barcodes = barcodeRepository.findByTaskId(taskId);
            if (!barcodes.isEmpty()) {
                barcodeRepository.deleteAll(barcodes);
            }

            List<Comment> comments = commentRepository.findByTaskIdOrderByCreatedAtDesc(taskId);
            if (!comments.isEmpty()) {
                commentRepository.deleteAll(comments);
            }
        }
    }

    private List<CustomerInvoice> dedupeInvoices(List<CustomerInvoice> invoices) {
        Map<String, CustomerInvoice> unique = new LinkedHashMap<>();
        for (CustomerInvoice invoice : invoices) {
            if (invoice == null || invoice.getId() == null) {
                continue;
            }
            unique.putIfAbsent(invoice.getId(), invoice);
        }
        return new ArrayList<>(unique.values());
    }

    private List<Customer> seedDemoCustomers(User demoUser) {
        ObjectId demoUserId = demoUser.getId();

        Customer firstCustomer = new Customer();
        firstCustomer.setTitle("Frau");
        firstCustomer.setCompanyName("Demo Bau GmbH");
        firstCustomer.setFirstname("Anna");
        firstCustomer.setLastName("Sommer");
        firstCustomer.setTelephone("030 1234567");
        firstCustomer.setMail("anna.sommer@demo.invalid");
        firstCustomer.setStreet("Musterstrasse");
        firstCustomer.setHouseNumber("12");
        firstCustomer.setZip("10115");
        firstCustomer.setCity("Berlin");
        firstCustomer.setCreatedBy(demoUserId);
        firstCustomer.setOwner(demoUserId);

        Customer secondCustomer = new Customer();
        secondCustomer.setTitle("Herr");
        secondCustomer.setCompanyName("Nordhandel AG");
        secondCustomer.setFirstname("Lukas");
        secondCustomer.setLastName("Becker");
        secondCustomer.setTelephone("040 7654321");
        secondCustomer.setMail("lukas.becker@demo.invalid");
        secondCustomer.setStreet("Hafenallee");
        secondCustomer.setHouseNumber("8");
        secondCustomer.setZip("20095");
        secondCustomer.setCity("Hamburg");
        secondCustomer.setCreatedBy(demoUserId);
        secondCustomer.setOwner(demoUserId);

        return customerRepository.saveAll(List.of(firstCustomer, secondCustomer));
    }

    private List<Service> seedDemoServices(User demoUser) {
        String demoUserId = demoUser.getId().toHexString();

        Service activeService = createFlatRateService(demoUserId, "Expresszustellung", "149.00");
        Service completedService = createFlatRateService(demoUserId, "Konsolidierungszustellung", "219.00");

        return serviceRepository.saveAll(List.of(activeService, completedService));
    }

    private void seedDemoJobs(User demoUser, List<Customer> customers, List<Service> services) {
        Map<String, Customer> customersByCompany = new LinkedHashMap<>();
        for (Customer customer : customers) {
            customersByCompany.put(customer.getCompanyName(), customer);
        }
        Map<String, Service> servicesByName = new LinkedHashMap<>();
        for (Service service : services) {
            servicesByName.put(service.getName(), service);
        }

        LocalDateTime now = LocalDateTime.now();
        String demoUserId = demoUser.getId().toHexString();

        Job activeJob = new Job();
        activeJob.setJobNumber("DEMO-AKTIV-001");
        activeJob.setStatus(JobStatus.IN_PROGRESS);
        activeJob.setCreatedAt(now.minusDays(2));
        activeJob.setUpdatedAt(now.minusHours(3));
        activeJob.setCreatedBy(demoUserId);
        activeJob.setDraft(false);
        activeJob.setDigitalProcessing(false);
        activeJob.setPickupDate(LocalDate.now().minusDays(1));
        activeJob.setPickupTime(LocalTime.of(9, 30));
        activeJob.setRemark("Demo-Auftrag aktiv");
        activeJob.setPrice(new BigDecimal("149.00"));
        applyCustomerToPickup(activeJob, customersByCompany.get("Demo Bau GmbH"));
        activeJob.setCustomerSelection("Demo Bau GmbH | Anna Sommer");
        activeJob.setDeliveryStations(new ArrayList<>(List.of(
                createDeliveryStation("Potsdam Expresslager", "Herr", "Timo", "Kranz", "0331 444555",
                        "Lange Bruecke", "4", null, "14467", "Potsdam", LocalDate.now(), LocalTime.of(14, 0), 0))));
        activeJob.setServiceIds(new ArrayList<>(List.of(servicesByName.get("Expresszustellung").getId())));
        activeJob.setSelectedServices(new ArrayList<>(List.of(
                createJobServiceSelection(servicesByName.get("Expresszustellung"), 0))));
        activeJob.syncFlatDeliveryFieldsFromStations();

        Job completedJob = new Job();
        completedJob.setJobNumber("DEMO-ERLEDIGT-001");
        completedJob.setStatus(JobStatus.COMPLETED);
        completedJob.setCreatedAt(now.minusDays(8));
        completedJob.setUpdatedAt(now.minusDays(5));
        completedJob.setCreatedBy(demoUserId);
        completedJob.setDraft(false);
        completedJob.setDigitalProcessing(false);
        completedJob.setPickupDate(LocalDate.now().minusDays(7));
        completedJob.setPickupTime(LocalTime.of(8, 15));
        completedJob.setRemark("Demo-Auftrag abgeschlossen");
        completedJob.setPrice(new BigDecimal("219.00"));
        applyCustomerToPickup(completedJob, customersByCompany.get("Nordhandel AG"));
        completedJob.setCustomerSelection("Nordhandel AG | Lukas Becker");
        completedJob.setDeliveryStations(new ArrayList<>(List.of(
                createDeliveryStation("Bremen Konsolidierung", "Frau", "Mara", "Jensen", "0421 987654", "Kontorweg",
                        "19", null, "28195", "Bremen", LocalDate.now().minusDays(6), LocalTime.of(16, 30), 0))));
        completedJob.setServiceIds(new ArrayList<>(List.of(servicesByName.get("Konsolidierungszustellung").getId())));
        completedJob.setSelectedServices(new ArrayList<>(List.of(
                createJobServiceSelection(servicesByName.get("Konsolidierungszustellung"), 0))));
        completedJob.syncFlatDeliveryFieldsFromStations();

        jobRepository.saveAll(List.of(activeJob, completedJob));
    }

    private Service createFlatRateService(String userId, String name, String price) {
        Service service = new Service();
        service.setId(new ObjectId().toHexString());
        service.setUserId(userId);
        service.setName(name);
        service.setCalculationBasis(Service.CalculationBasis.FLAT_RATE);
        service.setPrice(new BigDecimal(price));
        service.setMandatory(false);
        return service;
    }

    private JobServiceSelection createJobServiceSelection(Service service, int deliveryStationOrder) {
        JobServiceSelection selection = new JobServiceSelection();
        selection.setServiceId(service.getId());
        selection.setDeliveryStationOrder(deliveryStationOrder);
        return selection;
    }

    private void seedDemoInvoiceTemplate(User demoUser) {
        InvoiceTemplate template = new InvoiceTemplate(demoUser.getId().toHexString(), "Einfach",
                createDemoInvoiceTemplateData());
        invoiceTemplateRepository.save(template);
    }

    private String createDemoInvoiceTemplateData() {
        try {
            String templateJson = new String(
                    new ClassPathResource(DEMO_INVOICE_TEMPLATE_RESOURCE).getInputStream().readAllBytes(),
                    StandardCharsets.UTF_8);
            return new ObjectMapper().writeValueAsString(templateJson);
        } catch (IOException ex) {
            throw new IllegalStateException("Could not load demo invoice template", ex);
        }
    }

    private void applyCustomerToPickup(Job job, Customer customer) {
        if (job == null || customer == null) {
            return;
        }
        job.setPickupCompany(customer.getCompanyName());
        job.setPickupSalutation(customer.getTitle());
        job.setPickupFirstName(customer.getFirstname());
        job.setPickupLastName(customer.getLastName());
        job.setPickupPhone(customer.getTelephone());
        job.setPickupStreet(customer.getStreet());
        job.setPickupHouseNumber(customer.getHouseNumber());
        job.setPickupAddressAddition(customer.getAddressAddition());
        job.setPickupZip(customer.getZip());
        job.setPickupCity(customer.getCity());
    }

    private DeliveryStation createDeliveryStation(String company, String salutation, String firstName, String lastName,
            String phone, String street, String houseNumber, String addressAddition, String zip, String city,
            LocalDate deliveryDate, LocalTime deliveryTime, int stationOrder) {
        DeliveryStation station = new DeliveryStation();
        station.ensureStationId();
        station.setStationOrder(stationOrder);
        station.setCompany(company);
        station.setSalutation(salutation);
        station.setFirstName(firstName);
        station.setLastName(lastName);
        station.setPhone(phone);
        station.setStreet(street);
        station.setHouseNumber(houseNumber);
        station.setAddressAddition(addressAddition);
        station.setZip(zip);
        station.setCity(city);
        station.setDeliveryDate(deliveryDate);
        station.setDeliveryTime(deliveryTime);
        station.setTasks(new ArrayList<>());
        return station;
    }

    private User getDemoUser() {
        return userRepository.findByEmail(DEMO_USERNAME)
                .orElseThrow(() -> new IllegalStateException("Demo user does not exist"));
    }

    private void applyDemoUserDefaults(User demoUser) {
        LocalDateTime now = LocalDateTime.now();
        if (demoUser.getCreatedAt() == null) {
            demoUser.setCreatedAt(now);
        }

        demoUser.setEmail(DEMO_USERNAME);
        demoUser.setPassword(passwordEncoder.encode(DEMO_PASSWORD));
        demoUser.setFirstname(DEMO_FIRST_NAME);
        demoUser.setName(DEMO_LAST_NAME);
        demoUser.setTitle(null);
        demoUser.setCompany(null);
        demoUser.setCompanyAddition(null);
        demoUser.setStreet(null);
        demoUser.setHouseNumber(null);
        demoUser.setAddressAddition(null);
        demoUser.setZip(null);
        demoUser.setCity(null);
        demoUser.setDiffInvoiceAddress(false);
        demoUser.setInvCompany(null);
        demoUser.setInvCompanyAddition(null);
        demoUser.setInvFirstname(null);
        demoUser.setInvLastname(null);
        demoUser.setInvStreet(null);
        demoUser.setInvHouseNumber(null);
        demoUser.setInvAddressAddition(null);
        demoUser.setInvZip(null);
        demoUser.setInvCity(null);
        demoUser.setPhone(null);
        demoUser.setPhone2(null);
        demoUser.setFax(null);
        demoUser.setPasswordCode(null);
        demoUser.setPasswordTimestamp(null);
        demoUser.setIsActivated((byte) 1);
        demoUser.setIsEmailConfirmed((byte) 1);
        demoUser.setRoles(Set.of("USER"));
        demoUser.setDigitalProcessingEnabled(true);
        demoUser.setLocationTrackingEnabled(true);
        demoUser.setTwoFactorEnabled(false);
        demoUser.setLanguage(Language.DE);
        demoUser.setUpdatedAt(now);
    }
}
